JACS: JSON Agent Communication Standard

Welcome to the JSON Agent Communication Standard (JACS) documentation! JACS is a comprehensive framework for creating, signing, and verifying JSON documents with cryptographic integrity, designed specifically for AI agent communication and task management.

What is JACS?

JACS provides a standard way for AI agents to:

  • Create and sign JSON documents with cryptographic signatures
  • Verify authenticity and integrity of documents
  • Manage tasks and agreements between multiple agents
  • Maintain audit trails of modifications and versioning
  • Ensure trust in multi-agent systems

As a developer, JACS gives you the tools to build trustworthy AI systems where agents can securely exchange tasks, agreements, and data with verifiable integrity.

Key Features

  • 🔐 Cryptographic Security: RSA, Ed25519, and post-quantum cryptographic algorithms
  • 📋 JSON Schema Validation: Enforced document structure and validation
  • 🤝 Multi-Agent Agreements: Built-in support for agent collaboration and task agreements
  • 🔍 Full Audit Trail: Complete versioning and modification history
  • 🌐 Multiple Language Support: Rust, Node.js, and Python implementations
  • 🔌 MCP Integration: Native Model Context Protocol support
  • 📊 Observability: Built-in logging and metrics for production systems

Available Implementations

JACS is available in three languages, each with its own strengths:

🦀 Rust (Core Library + CLI)

  • Performance: Fastest implementation with native performance
  • CLI Tool: Complete command-line interface for agent and document management
  • Library: Full-featured Rust library for embedded applications
  • Observability: Advanced logging and metrics with OpenTelemetry support

🟢 Node.js (jacsnpm)

  • Web Integration: Perfect for web servers and Express.js applications
  • MCP Support: Native Model Context Protocol integration
  • HTTP Server: Built-in HTTP server capabilities
  • NPM Package: Easy installation and integration

🐍 Python (jacspy)

  • AI/ML Integration: Ideal for AI and machine learning workflows
  • FastMCP: Advanced MCP server implementations
  • PyPI Package: Simple pip install integration
  • Data Science: Perfect for Jupyter notebooks and data pipelines

Quick Start

Choose your implementation and get started in minutes:

Rust CLI

cargo install jacs
jacs init  # Create config, keys, and agent

Or step by step:

jacs config create
jacs agent create --create-keys true

Node.js

npm install jacsnpm
import { JacsAgent } from 'jacsnpm';

const agent = new JacsAgent();
agent.load('./config.json');

Python

pip install jacs
import jacs

agent = jacs.JacsAgent()
agent.load("./config.json")

When to Use JACS

JACS is ideal for scenarios where you need:

  • Multi-agent systems where agents need to trust each other
  • Task delegation with verifiable completion and approval
  • Audit trails for AI decision-making processes
  • Secure data exchange between AI systems
  • Compliance requirements for AI system interactions
  • Version control for AI-generated content and decisions

Why JACS?

🎯 Agent-Focused Design

Unlike general-purpose signing frameworks, JACS is specifically designed for AI agent communication patterns - tasks, agreements, and collaborative workflows.

🚀 Production Ready

With built-in observability, multiple storage backends, and comprehensive error handling, JACS is ready for production AI systems.

🔒 Future-Proof Security

Support for both current (RSA, Ed25519) and post-quantum cryptographic algorithms ensures your system remains secure.

🌐 Universal Compatibility

JSON-based documents work everywhere - store them in any database, transmit over any protocol, integrate with any system.

🧩 Flexible Integration

Whether you're building a simple CLI tool or a complex multi-agent system, JACS adapts to your architecture.

Getting Started

  1. Core Concepts - Understand agents, documents, and agreements
  2. Quick Start Guide - Get up and running in minutes
  3. Choose Your Implementation:

Community and Support

  • GitHub: HumanAssisted/JACS
  • Issues: Report bugs and feature requests
  • Examples: Complete examples for all implementations
  • Documentation: This comprehensive guide

Ready to build trustworthy AI systems? Let's get started!

What is JACS?

JACS (JSON Agent Communication Standard) is a comprehensive framework designed to solve a critical problem in AI systems: How do agents communicate and collaborate securely with verifiable trust?

The Problem JACS Solves

As AI systems become more sophisticated, we're moving toward multi-agent architectures where different AI agents need to:

  • Exchange tasks and delegate work to each other
  • Create agreements and verify their completion
  • Share data with guaranteed authenticity
  • Maintain audit trails of decisions and actions
  • Establish trust without centralized authorities

Traditional approaches fall short because they lack:

  • Cryptographic integrity for agent communications
  • Standardized formats for agent interactions
  • Built-in versioning and audit trails
  • Support for multi-agent agreements and workflows

JACS Core Philosophy

🎯 Agent-First Design

JACS is built specifically for AI agent communication patterns, not as a general-purpose document signing system. It understands concepts like:

  • Agents with identities and capabilities
  • Tasks that can be delegated and tracked
  • Agreements between multiple parties
  • Versioning for iterative improvements

🔐 Trust Through Cryptography

Every JACS document includes:

  • Digital signatures proving authenticity
  • Hash verification ensuring integrity
  • Public key cryptography for identity verification
  • Timestamps for chronological ordering

📋 Standards-Based

JACS builds on proven standards:

  • JSON for universal compatibility
  • JSON Schema for structure validation
  • RFC 3339 timestamps for consistency
  • Standard cryptographic algorithms (RSA, Ed25519, post-quantum)

Key Concepts

Agents

An Agent is an autonomous entity with:

  • A unique identity (UUID)
  • Cryptographic keys for signing
  • Capabilities defined in services
  • The ability to create and verify documents

Documents

A Document is any JSON object that includes:

  • JACS header fields (ID, version, creator, etc.)
  • A cryptographic signature
  • A hash for integrity verification
  • Business logic specific to the document type

Tasks

A Task is a special document type representing:

  • Work to be performed
  • Success/failure criteria
  • Input/output specifications
  • Delegation and completion tracking

Agreements

An Agreement is a mechanism for:

  • Multiple agents to consent to terms
  • Tracking signatures from all required parties
  • Ensuring all participants have signed before proceeding
  • Creating binding commitments between agents

How JACS Works

graph TD
    A[Agent A] -->|Creates Task| T[JACS Task Document]
    T -->|Contains| S[Digital Signature]
    T -->|Contains| H[SHA256 Hash]
    T -->|Contains| M[Metadata]
    
    A -->|Sends to| B[Agent B]
    B -->|Verifies| T
    B -->|Signs Agreement| AG[Agreement Document]
    AG -->|Returns to| A
  1. Agent A creates a task document with their requirements
  2. The document is signed with Agent A's private key
  3. A hash is calculated for integrity verification
  4. Agent B receives and verifies the signature and hash
  5. Agent B can create an agreement to accept the task
  6. Both agents have a verifiable record of the interaction

Real-World Examples

🤖 AI Content Pipeline

Content Agent → Research Agent → Review Agent → Publishing Agent

Each handoff includes signed task documents with clear requirements and deliverables.

📊 Data Processing Workflow

Data Ingestion Agent → Processing Agent → Validation Agent → Storage Agent

Each step is tracked with verifiable completion certificates and quality metrics.

🔍 Multi-Agent Analysis

Query Agent → Research Agent → Analysis Agent → Reporting Agent

Complex analysis tasks are broken down with clear accountability for each step.

Benefits Over Alternatives

FeatureJACSTraditional APIsGeneral Signing
Agent Identity✅ Built-in❌ Custom implementation❌ Not agent-focused
Task Management✅ Native support❌ Application-specific❌ Not applicable
Multi-Party Agreements✅ Core feature❌ Complex to implement⚠️ Possible but difficult
Audit Trails✅ Automatic❌ Manual logging⚠️ Basic signing only
Schema Validation✅ JSON Schema❌ Custom validation❌ No structure
Versioning✅ Built-in❌ Manual versioning❌ Not supported
Cross-Platform✅ JSON everywhere⚠️ Protocol dependent⚠️ Format dependent

When to Use JACS

Perfect for:

  • Multi-agent AI systems
  • Task delegation and tracking
  • Audit trail requirements
  • Cross-organization AI collaboration
  • Compliance-critical AI applications
  • Research environments with multiple AI models

⚠️ Consider alternatives for:

  • Simple single-agent systems
  • Real-time streaming data
  • High-frequency micro-transactions
  • Systems where trust is not a concern

Next Steps

Ready to dive deeper? Continue with:

Core Concepts

Understanding JACS requires familiarity with several key concepts that work together to create a secure, verifiable communication framework for AI agents.

Agents

An Agent is the fundamental entity in JACS - an autonomous participant that can create, sign, and verify documents.

Agent Identity

{
  "jacsId": "550e8400-e29b-41d4-a716-446655440000",
  "jacsVersion": "123e4567-e89b-12d3-a456-426614174000",
  "jacsType": "agent",
  "name": "Content Creation Agent",
  "description": "Specialized in creating marketing content"
}

Key Properties:

  • jacsId: Permanent UUID identifying the agent
  • jacsVersion: UUID that changes with each update
  • Cryptographic Keys: Ed25519, RSA, or post-quantum key pairs
  • Services: Capabilities the agent offers
  • Contacts: How to reach the agent

Agent Lifecycle

  1. Creation: Generate keys and initial agent document
  2. Registration: Store public keys for verification
  3. Operation: Create and sign documents
  4. Updates: Version changes while maintaining identity
  5. Verification: Other agents validate signatures

Documents

A Document is any JSON object that follows JACS conventions for identity, versioning, and cryptographic integrity.

Document Structure

{
  "jacsId": "doc-uuid-here",
  "jacsVersion": "version-uuid-here",
  "jacsType": "task",
  "jacsVersionDate": "2024-01-15T10:30:00Z",
  "jacsPreviousVersion": "previous-version-uuid",
  
  "title": "Analyze Q4 Sales Data",
  "description": "Generate insights from sales data",
  
  "jacsSha256": "hash-of-document-content",
  "jacsSignature": {
    "agentID": "agent-uuid",
    "agentVersion": "agent-version-uuid",
    "signature": "base64-signature",
    "signingAlgorithm": "ring-Ed25519",
    "publicKeyHash": "hash-of-public-key",
    "date": "2024-01-15T10:30:00Z",
    "fields": ["jacsId", "title", "description"]
  }
}

Required JACS Fields

FieldPurposeExample
$schemaJSON Schema referenceURL to schema
jacsIdPermanent document identifierUUID v4
jacsVersionVersion identifier (changes on update)UUID v4
jacsTypeDocument type"agent", "task", "message"
jacsVersionDateWhen this version was createdRFC 3339 timestamp
jacsOriginalVersionOriginal version UUIDUUID v4
jacsOriginalDateOriginal creation timestampRFC 3339 timestamp
jacsLevelData level/intent"raw", "config", "artifact", "derived"
jacsPreviousVersionPrevious version UUID (optional)UUID v4 or null
jacsSha256Hash of document contentSHA-256 hex string
jacsSignatureCryptographic signatureSignature object

Document Types

Agent Documents

  • Define agent identity and capabilities
  • Contain service definitions and contact information
  • Self-signed by the agent

Task Documents

  • Describe work to be performed
  • Include success/failure criteria
  • Can be delegated between agents

Message Documents

  • General communication between agents
  • Can include attachments and metadata
  • Support threaded conversations

Agreement Documents

  • Multi-party consent mechanisms
  • Track required and actual signatures
  • Enforce completion before proceeding

Tasks

Tasks represent work that can be delegated, tracked, and verified between agents.

Task Structure

{
  "jacsType": "task",
  "title": "Generate Marketing Copy",
  "description": "Create compelling copy for product launch",
  
  "actions": [
    {
      "id": "research",
      "name": "Research competitors",
      "description": "Analyze competitor messaging",
      "success": "Complete competitive analysis report",
      "failure": "Unable to access competitor data"
    }
  ],
  
  "jacsTaskCustomer": {
    "agentID": "customer-agent-uuid",
    "signature": "customer-signature"
  }
}

Task Lifecycle

  1. Creation: Customer agent creates task with requirements
  2. Delegation: Task sent to service provider agent
  3. Agreement: Provider signs agreement to accept task
  4. Execution: Provider performs the work
  5. Completion: Provider creates completion document
  6. Verification: Customer verifies and accepts results

Task Components

Actions: Individual steps within a task

  • id: Unique identifier within the task
  • name: Human-readable action name
  • description: Detailed requirements
  • success: Definition of successful completion
  • failure: What constitutes failure

Services: Required capabilities

  • type: Service category
  • requirements: Specific needs
  • constraints: Limitations or restrictions

Agreements

Agreements enable multi-party consent and coordination between agents.

Agreement Structure

{
  "jacsType": "agreement",
  "title": "Task Acceptance Agreement",
  "question": "Do you agree to complete the marketing copy task?",
  "context": "Task ID: abc123, Deadline: 2024-01-20",
  
  "agents": [
    "agent-1-uuid",
    "agent-2-uuid",
    "agent-3-uuid"
  ],
  
  "jacsAgreement": {
    "agent-1-uuid": {
      "agentID": "agent-1-uuid",
      "signature": "base64-signature",
      "date": "2024-01-15T10:30:00Z"
    },
    "agent-2-uuid": {
      "agentID": "agent-2-uuid", 
      "signature": "base64-signature",
      "date": "2024-01-15T11:15:00Z"
    }
    // agent-3-uuid has not signed yet
  },
  
  "jacsAgreementHash": "hash-of-agreement-content"
}

Agreement Process

  1. Creation: Initial agent creates agreement with required participants
  2. Distribution: Agreement sent to all required agents
  3. Review: Each agent reviews terms and conditions
  4. Signing: Agents add their signatures if they consent
  5. Completion: Agreement becomes binding when all parties have signed
  6. Verification: Any party can verify all signatures

Agreement Types

Task Agreements: Consent to perform specific work Service Agreements: Long-term service provision contracts
Data Sharing Agreements: Permission to access or use data Update Agreements: Consent to system or process changes

Cryptographic Security

JACS uses industry-standard cryptographic primitives for security.

Supported Algorithms

Current Standards

  • ring-Ed25519: Fast elliptic curve signatures using the ring library (recommended)
  • RSA-PSS: Traditional RSA with probabilistic signature scheme

Post-Quantum

  • pq-dilithium: NIST-standardized post-quantum signatures

Signature Process

  1. Content Extraction: Specific fields are extracted for signing
  2. Canonicalization: Fields are sorted and formatted consistently
  3. Hashing: SHA-256 hash of the canonical content
  4. Signing: Private key signs the hash
  5. Verification: Public key verifies the signature

Key Management

  • Agent Keys: Each agent has a unique key pair
  • Public Key Distribution: Public keys shared through secure channels
  • Key Rotation: Agents can update keys while maintaining identity
  • Key Verification: Public key hashes ensure integrity

Versioning and Audit Trails

JACS provides comprehensive versioning for tracking document evolution.

Version Management

  • Immutable IDs: jacsId never changes for a document
  • Version IDs: jacsVersion changes with each update
  • Previous Versions: jacsPreviousVersion creates a chain
  • Timestamps: jacsVersionDate provides chronological order

Audit Trail Benefits

  • Complete History: Track all changes to any document
  • Attribution: Know exactly who made each change
  • Verification: Cryptographic proof of authenticity
  • Compliance: Meet regulatory audit requirements

Storage and Transport

JACS documents are designed to be storage and transport agnostic.

Storage Options

  • File System: Simple JSON files
  • Databases: Store as JSON/JSONB fields
  • Object Storage: S3, Azure Blob, Google Cloud Storage
  • Version Control: Git repositories for change tracking

Transport Mechanisms

  • HTTP APIs: RESTful or GraphQL endpoints
  • Message Queues: RabbitMQ, Kafka, SQS
  • Email: Documents as attachments
  • Direct Transfer: USB drives, file sharing

Format Compatibility

  • JSON: Universal compatibility across all systems
  • Schema Validation: Ensures consistent structure
  • Self-Contained: All necessary information in the document
  • Human Readable: Can be inspected and debugged easily

Next Steps

Now that you understand the core concepts:

  1. Quick Start - Try JACS hands-on
  2. Choose Implementation:
  3. Examples - See real-world usage patterns

Quick Start Guide

This guide will get you up and running with JACS in under 10 minutes. We'll create an agent, generate a task, and demonstrate the core workflow across all three implementations.

Choose Your Implementation

Select the implementation that best fits your needs:

Install Rust CLI

# Install from crates.io
cargo install jacs

# Or build from source
git clone https://github.com/HumanAssisted/JACS
cd JACS/jacs
cargo install --path . --features="cli"

Initialize JACS

# Create configuration and agent in one step
jacs init

# This creates:
# - ~/.jacs/config.json
# - Agent keys and documents
# - Basic directory structure

Create Your First Agent

# Create an agent (if not done via jacs init)
# Agent type is defined in the input JSON file or default template
jacs agent create --create-keys true

# Or provide a custom agent definition file
jacs agent create --create-keys true -f my-agent.json

# Verify your agent was created correctly
jacs agent verify

Create and Sign a Task

# Create a task document with name and description
jacs task create \
  -n "Write Product Description" \
  -d "Create compelling copy for new product launch"

# The task is automatically signed by your agent

Install Node.js Package

npm install jacsnpm

Basic Setup

import { JacsAgent, createConfig } from 'jacsnpm';
import fs from 'fs';

// Create configuration
const config = {
  jacs_agent_id_and_version: null,
  jacs_data_directory: "./jacs_data",
  jacs_key_directory: "./jacs_keys",
  jacs_default_storage: "fs",
  jacs_agent_key_algorithm: "ring-Ed25519"
};

// Save config
fs.writeFileSync('./jacs.config.json', JSON.stringify(config, null, 2));

// Create agent instance and load configuration
const agent = new JacsAgent();
agent.load('./jacs.config.json');

Create Agent Document

// Create agent with services
const agentData = {
  name: "Content Creator Bot",
  description: "AI agent specialized in content creation",
  services: [
    {
      type: "content_generation",
      name: "Product Description Writer",
      description: "Creates compelling product descriptions",
      success: "Engaging copy that converts visitors",
      failure: "Generic or low-quality content"
    }
  ]
};

// Generate keys and create agent
await agent.generateKeys();
const agentDoc = await agent.createAgent(agentData);
console.log('Agent created:', agentDoc.jacsId);

Create a Task

// Create task document
const task = {
  title: "Write Product Description",
  description: "Create compelling copy for new product launch",
  actions: [
    {
      id: "research",
      name: "Product Research", 
      description: "Analyze product features and benefits",
      success: "Complete understanding of product value",
      failure: "Insufficient product knowledge"
    },
    {
      id: "write",
      name: "Write Copy",
      description: "Create engaging product description",
      success: "200-word compelling description",
      failure: "Generic or unconvincing copy"
    }
  ]
};

// Sign and create task
const signedTask = await agent.createTask(task);
console.log('Task created:', signedTask.jacsId);

Install Python Package

pip install jacs

Basic Setup

import jacs
import json
import os

# Create configuration
config = {
    "jacs_agent_id_and_version": None,
    "jacs_data_directory": "./jacs_data",
    "jacs_key_directory": "./jacs_keys",
    "jacs_default_storage": "fs",
    "jacs_agent_key_algorithm": "ring-Ed25519"
}

# Ensure directories exist
os.makedirs("./jacs_data", exist_ok=True)
os.makedirs("./jacs_keys", exist_ok=True)

# Save config
with open('jacs.config.json', 'w') as f:
    json.dump(config, f, indent=2)

# Create agent instance and load configuration
agent = jacs.JacsAgent()
agent.load("./jacs.config.json")

Create Agent Document

# Define agent capabilities
agent_data = {
    "name": "Content Creator Bot",
    "description": "AI agent specialized in content creation",
    "services": [
        {
            "type": "content_generation",
            "name": "Product Description Writer", 
            "description": "Creates compelling product descriptions",
            "success": "Engaging copy that converts visitors",
            "failure": "Generic or low-quality content"
        }
    ]
}

# Generate keys and create agent
agent.generate_keys()
agent_doc = agent.create_agent(agent_data)
print(f'Agent created: {agent_doc["jacsId"]}')

Create a Task

# Define task
task = {
    "title": "Write Product Description",
    "description": "Create compelling copy for new product launch",
    "actions": [
        {
            "id": "research",
            "name": "Product Research",
            "description": "Analyze product features and benefits", 
            "success": "Complete understanding of product value",
            "failure": "Insufficient product knowledge"
        },
        {
            "id": "write", 
            "name": "Write Copy",
            "description": "Create engaging product description",
            "success": "200-word compelling description",
            "failure": "Generic or unconvincing copy"
        }
    ]
}

# Sign and create task
signed_task = agent.create_task(task)
print(f'Task created: {signed_task["jacsId"]}')

Understanding What Happened

When you completed the quick start, several important things occurred:

1. Agent Creation

  • A unique identity (UUID) was generated for your agent
  • Cryptographic key pair was created for signing
  • Agent document was created and self-signed
  • Public key was stored for verification

2. Configuration Setup

  • Storage directories were configured
  • Cryptographic algorithm was selected
  • Agent identity was linked to configuration

3. Task Creation

  • Task document was structured according to JACS schema
  • Document was signed with your agent's private key
  • SHA-256 hash was calculated for integrity
  • Signature metadata was embedded in the document

Verify Everything Works

Let's verify that the documents are properly signed and can be validated:

# Verify agent signature
jacs agent verify

# Verify a specific document
jacs document verify -f ./jacs_data/[document-id].json

# Sign a document
jacs document sign -f ./jacs_data/[document-id].json
// Verify agent signature
const isValid = await agent.verifyAgent();
console.log('Agent signature valid:', isValid);

// List all documents
const documents = await agent.listDocuments();
console.log('Documents:', documents.length);

// Verify task signature
const taskValid = await agent.verifyDocument(signedTask);
console.log('Task signature valid:', taskValid);

// Get document details
const taskDetails = await agent.getDocument(signedTask.jacsId);
console.log('Task details:', taskDetails);
# Verify agent signature
is_valid = agent.verify_agent()
print(f'Agent signature valid: {is_valid}')

# List all documents
documents = agent.list_documents()
print(f'Documents: {len(documents)}')

# Verify task signature  
task_valid = agent.verify_document(signed_task)
print(f'Task signature valid: {task_valid}')

# Get document details
task_details = agent.get_document(signed_task["jacsId"])
print(f'Task details: {task_details}')

Next Steps: Multi-Agent Workflow

Now let's create a second agent and demonstrate inter-agent communication:

# Create a second agent configuration
cp jacs.config.json reviewer.config.json
# Edit reviewer.config.json to set jacs_agent_id_and_version to null

# Create reviewer agent (uses JACS_CONFIG_PATH environment variable)
JACS_CONFIG_PATH=./reviewer.config.json jacs agent create --create-keys true

# Create an agreement on a document
jacs agreement create -f ./document.json \
  --agents [agent-1-id],[agent-2-id] \
  --question "Do you agree to collaborate on this content task?"

# Sign the agreement as first agent
jacs agreement sign -f ./document.json

# Sign as second agent (using reviewer config)
JACS_CONFIG_PATH=./reviewer.config.json jacs agreement sign -f ./document.json

# Verify agreement is complete
jacs agreement check -f ./document.json
// Create second agent with separate config file
const reviewerConfig = { ...config };
reviewerConfig.jacs_agent_id_and_version = null;

fs.writeFileSync('./reviewer.config.json', JSON.stringify(reviewerConfig, null, 2));

const reviewer = new JacsAgent();
reviewer.load('./reviewer.config.json');
await reviewer.generateKeys();

const reviewerDoc = await reviewer.createAgent({
  name: "Content Reviewer Bot",
  description: "AI agent specialized in content review"
});

// Create agreement between agents
const agreement = {
  title: "Content Collaboration Agreement",
  question: "Do you agree to collaborate on this content task?",
  context: `Task: ${signedTask.jacsId}`,
  agents: [agentDoc.jacsId, reviewerDoc.jacsId]
};

const signedAgreement = await agent.createAgreement(agreement);

// Both agents sign the agreement
await agent.signAgreement(signedAgreement.jacsId);
await reviewer.signAgreement(signedAgreement.jacsId);

// Verify all signatures
const agreementValid = await agent.verifyAgreement(signedAgreement.jacsId);
console.log('Agreement complete:', agreementValid);
# Create second agent with separate config file
reviewer_config = config.copy()
reviewer_config["jacs_agent_id_and_version"] = None

with open('reviewer.config.json', 'w') as f:
    json.dump(reviewer_config, f, indent=2)

reviewer = jacs.JacsAgent()
reviewer.load("./reviewer.config.json")
reviewer.generate_keys()

reviewer_doc = reviewer.create_agent({
    "name": "Content Reviewer Bot", 
    "description": "AI agent specialized in content review"
})

# Create agreement between agents
agreement = {
    "title": "Content Collaboration Agreement",
    "question": "Do you agree to collaborate on this content task?",
    "context": f"Task: {signed_task['jacsId']}",
    "agents": [agent_doc["jacsId"], reviewer_doc["jacsId"]]
}

signed_agreement = agent.create_agreement(agreement)

# Both agents sign the agreement
agent.sign_agreement(signed_agreement["jacsId"])
reviewer.sign_agreement(signed_agreement["jacsId"])

# Verify all signatures
agreement_valid = agent.verify_agreement(signed_agreement["jacsId"])
print(f'Agreement complete: {agreement_valid}')

What You've Accomplished

Congratulations! You've successfully:

Created JACS agents with cryptographic identities ✅ Generated and signed documents with verifiable integrity
Established multi-agent agreements with cryptographic consent ✅ Verified signatures and document authenticity ✅ Created an audit trail of all interactions

Key Takeaways

  • Everything is verifiable: All documents have cryptographic signatures
  • Agents are autonomous: Each has its own identity and keys
  • Agreements enable trust: Multi-party consent before proceeding
  • Audit trails are automatic: Complete history of all interactions
  • JSON is universal: Documents work everywhere

Where to Go Next

Now that you have the basics working:

  1. Rust Deep Dive - Learn the full Rust API
  2. Node.js Integration - Add MCP support
  3. Python FastMCP - Build MCP servers
  4. Production Setup - Add monitoring and logging
  5. Real Examples - See production patterns

Troubleshooting

Agent creation fails: Check that the data and key directories exist and are writable Signature verification fails: Ensure public keys are properly stored and accessible Agreement signing fails: Verify all agent IDs are correct and agents exist Documents not found: Check the data directory configuration

Need help? Check the GitHub issues or review the detailed implementation guides.

Installation

This guide covers installing the JACS Rust CLI and library.

Requirements

  • Rust: Version 1.93 or later (Edition 2024)
  • Cargo: Included with Rust installation

Verify Rust Version

rustc --version
# Should show rustc 1.93.0 or later

If you need to update Rust:

rustup update stable

Installing the CLI

cargo install jacs --features cli

From Source

git clone https://github.com/HumanAssisted/JACS
cd JACS/jacs
cargo install --path . --features cli

Verify Installation

jacs --help

Using as a Library

Add JACS to your Cargo.toml:

[dependencies]
jacs = "0.3"

With Optional Features

JACS supports several optional features for observability and integrations:

[dependencies]
# Basic library usage
jacs = "0.3"

# With OpenTelemetry logging
jacs = { version = "0.3", features = ["otlp-logs"] }

# With OpenTelemetry metrics
jacs = { version = "0.3", features = ["otlp-metrics"] }

# With OpenTelemetry tracing
jacs = { version = "0.3", features = ["otlp-tracing"] }

# With all observability features
jacs = { version = "0.3", features = ["otlp-logs", "otlp-metrics", "otlp-tracing"] }

Available Features

FeatureDescription
cliEnables CLI binary build with clap and ratatui
otlp-logsOpenTelemetry Protocol logging backend
otlp-metricsOpenTelemetry Protocol metrics backend
otlp-tracingOpenTelemetry Protocol distributed tracing
observability-convenienceHelper wrappers for metrics and logging
mcp-serverModel Context Protocol server integration surface

Platform Support

JACS supports the following platforms:

PlatformArchitectureSupport
Linuxx86_64, aarch64Full support
macOSx86_64, aarch64Full support
Windowsx86_64Full support
WebAssemblywasm32Partial (no post-quantum crypto, limited storage)

WebAssembly Notes

When targeting WebAssembly, some features are unavailable:

  • Post-quantum cryptographic algorithms (pq-dilithium)
  • File system storage backend
  • HTTP-based remote operations

Configuration

After installation, initialize JACS:

# Create configuration and agent in one step
jacs init

This creates:

  • ~/.jacs/jacs.config.json - Configuration file
  • Cryptographic keys for your agent
  • Initial agent document

Manual Configuration

Alternatively, create configuration and agent separately:

# Create configuration only
jacs config create

# Create agent with keys
jacs agent create --create-keys true

Environment Variables

JACS respects the following environment variables:

VariableDescriptionDefault
JACS_CONFIG_PATHPath to configuration file./jacs.config.json
JACS_USE_SECURITYEnable/disable security featurestrue
JACS_DATA_DIRECTORYDirectory for document storage./jacs_data
JACS_KEY_DIRECTORYDirectory for cryptographic keys./jacs_keys
JACS_DEFAULT_STORAGEStorage backend (fs, memory)fs
JACS_AGENT_KEY_ALGORITHMKey algorithm (ring-Ed25519, RSA-PSS, pq-dilithium)ring-Ed25519

Troubleshooting

Build Errors

"edition 2024 is required" Update Rust to version 1.93 or later:

rustup update stable

Missing dependencies on Linux Install build essentials:

# Debian/Ubuntu
sudo apt-get install build-essential pkg-config libssl-dev

# Fedora
sudo dnf install gcc openssl-devel

Runtime Errors

"Configuration file not found" Run jacs init or set JACS_CONFIG_PATH environment variable.

"Key directory does not exist" Create the key directory or run jacs init:

mkdir -p ./jacs_keys

"Permission denied" Ensure you have write permissions to the data and key directories.

Next Steps

CLI Usage

The JACS CLI provides a command-line interface for managing agents, documents, tasks, and agreements.

Getting Help

# General help
jacs --help

# Command-specific help
jacs agent --help
jacs document --help
jacs task --help

Commands Overview

CommandDescription
jacs initInitialize JACS (create config and agent with keys)
jacs versionPrint version information
jacs configManage configuration
jacs agentManage agents
jacs documentManage documents
jacs taskManage tasks

Initialization

Quick Start

# Initialize everything in one step
jacs init

This command:

  1. Creates a configuration file (jacs.config.json)
  2. Generates cryptographic keys
  3. Creates an initial agent document

Configuration Commands

Create Configuration

jacs config create

Creates a new jacs.config.json file in the current directory with default settings.

Read Configuration

jacs config read

Displays the current configuration, including values from both the config file and environment variables.

Agent Commands

Create Agent

jacs agent create --create-keys true

# With a custom agent definition file
jacs agent create --create-keys true -f my-agent.json

# Without creating new keys (use existing)
jacs agent create --create-keys false -f my-agent.json

Options:

OptionShortRequiredDescription
--create-keysYesWhether to create new cryptographic keys
-fNoPath to JSON file with agent definition

Verify Agent

# Verify agent from config
jacs agent verify

# Verify specific agent file
jacs agent verify -a ./path/to/agent.json

# With DNS validation options
jacs agent verify --require-dns
jacs agent verify --require-strict-dns
jacs agent verify --no-dns
jacs agent verify --ignore-dns

Options:

OptionShortDescription
-a--agent-filePath to agent file (optional)
--no-dnsDisable DNS validation
--require-dnsRequire DNS validation (not strict)
--require-strict-dnsRequire DNSSEC validation
--ignore-dnsIgnore DNS validation entirely

DNS Commands

# Generate DNS TXT record commands for agent publishing
jacs agent dns --domain example.com --agent-id [uuid]

# With different output formats
jacs agent dns --domain example.com --encoding hex
jacs agent dns --domain example.com --provider aws

# With custom TTL
jacs agent dns --domain example.com --ttl 7200

Options:

OptionDefaultDescription
--domainDomain for DNS record
--agent-idAgent UUID (optional, uses config if not provided)
--ttl3600Time-to-live in seconds
--encodingbase64Encoding format (base64, hex)
--providerplainOutput format (plain, aws, azure, cloudflare)

Lookup Agent

# Look up another agent's public key from their domain
jacs agent lookup agent.example.com

# With strict DNSSEC validation
jacs agent lookup agent.example.com --strict

# Skip DNS lookup
jacs agent lookup agent.example.com --no-dns

Task Commands

Create Task

jacs task create -n "Task Name" -d "Task description"

# With optional agent file
jacs task create -n "Task Name" -d "Description" -a ./agent.json

# With input file
jacs task create -n "Task Name" -d "Description" -f ./task-details.json

Options:

OptionShortRequiredDescription
-n--nameYesName of the task
-d--descriptionYesDescription of the task
-a--agent-fileNoPath to agent file
-f--filenameNoPath to JSON file with additional task data

Document Commands

Create Document

# Create from a JSON file
jacs document create -f ./document.json

# Create from a directory of files
jacs document create -d ./documents/

# With custom schema
jacs document create -f ./document.json -s ./custom-schema.json

# With file attachments
jacs document create -f ./document.json --attach ./attachment.pdf

# Embed attachments in document
jacs document create -f ./document.json --attach ./files/ --embed true

# Output to specific file
jacs document create -f ./document.json -o ./output.json

# Print to stdout instead of saving
jacs document create -f ./document.json --no-save

Options:

OptionShortDescription
-f--filenamePath to input JSON file
-d--directoryPath to directory of JSON files
-o--outputOutput filename
-s--schemaPath to custom JSON schema
--attachPath to file/directory for attachments
--embed-eEmbed documents (true/false)
--no-save-nPrint to stdout instead of saving
-v--verboseEnable verbose output
-a--agent-filePath to agent file

Update Document

# Update an existing document with new content
jacs document update -f ./original.json -n ./updated.json

# With output file
jacs document update -f ./original.json -n ./updated.json -o ./result.json

# With file attachments
jacs document update -f ./original.json -n ./updated.json --attach ./new-file.pdf

Options:

OptionShortRequiredDescription
-f--filenameYesPath to original document
-n--newYesPath to new version
-o--outputNoOutput filename
--attachNoPath to file attachments
--embed-eNoEmbed documents (true/false)

Verify Document

# Verify a document
jacs document verify -f ./document.json

# Verify all documents in a directory
jacs document verify -d ./documents/

# With custom schema
jacs document verify -f ./document.json -s ./schema.json

# Verbose output
jacs document verify -f ./document.json -v

Options:

OptionShortDescription
-f--filenamePath to document file
-d--directoryPath to directory of documents
-s--schemaPath to JSON schema for validation
-v--verboseEnable verbose output
-a--agent-filePath to agent file

Extract Embedded Content

# Extract embedded content from a document
jacs document extract -f ./document.json

# Extract from all documents in directory
jacs document extract -d ./documents/

Agreement Commands

# Create an agreement requiring signatures from specified agents
jacs document create-agreement -f ./document.json -i agent1-uuid,agent2-uuid

# Check agreement status
jacs document check-agreement -f ./document.json

# Sign an agreement
jacs document sign-agreement -f ./document.json

Create Agreement Options:

OptionShortRequiredDescription
-f--filenameYesPath to document
-i--agentidsYesComma-separated list of agent UUIDs
-o--outputNoOutput filename
--no-save-nNoPrint to stdout

Environment Variables

The CLI respects the following environment variables:

# Use a specific configuration file
JACS_CONFIG_PATH=./custom-config.json jacs agent verify

# Override settings
JACS_DATA_DIRECTORY=./data jacs document create -f ./doc.json
JACS_KEY_DIRECTORY=./keys jacs agent create --create-keys true

Common Workflows

Create and Sign a Document

# 1. Initialize (if not done)
jacs init

# 2. Create document
jacs document create -f ./my-document.json

# 3. Verify the signed document
jacs document verify -f ./jacs_data/[document-id].json

Multi-Agent Agreement

# 1. Create agreement on a document
jacs document create-agreement -f ./document.json -i agent1-id,agent2-id

# 2. First agent signs
jacs document sign-agreement -f ./document.json

# 3. Second agent signs (using their config)
JACS_CONFIG_PATH=./agent2.config.json jacs document sign-agreement -f ./document.json

# 4. Check agreement is complete
jacs document check-agreement -f ./document.json

Verify Another Agent

# Look up agent by domain
jacs agent lookup other-agent.example.com

# Verify with strict DNS
jacs agent verify -a ./other-agent.json --require-strict-dns

Exit Codes

CodeMeaning
0Success
1General error
2Invalid arguments
3File not found
4Verification failed
5Signature invalid

Next Steps

Creating an Agent

An agent is the fundamental identity in JACS - an autonomous entity that can create, sign, and verify documents. This guide covers creating and managing agents.

What is an Agent?

A JACS agent is:

  • A unique identity with a UUID that never changes
  • A holder of cryptographic keys for signing
  • A provider of services defined in the agent document
  • Self-signed to prove authenticity

Creating Your First Agent

# Initialize JACS (creates config and agent)
jacs init

This creates:

  • Configuration file
  • Cryptographic key pair
  • Initial agent document

Manual Method

# 1. Create configuration
jacs config create

# 2. Create agent with new keys
jacs agent create --create-keys true

With Custom Agent Definition

Create an agent definition file (my-agent.json):

{
  "$schema": "https://hai.ai/schemas/agent/v1/agent.schema.json",
  "jacsAgentType": "ai",
  "jacsAgentDomain": "myagent.example.com",
  "name": "Content Creation Agent",
  "description": "AI agent specialized in content creation",
  "jacsServices": [
    {
      "jacsServiceName": "content-generation",
      "jacsServiceDescription": "Generate high-quality content",
      "jacsServiceSuccess": "Engaging, accurate content delivered",
      "jacsServiceFailure": "Unable to generate requested content"
    }
  ]
}

Then create the agent:

jacs agent create --create-keys true -f my-agent.json

Agent Types

JACS supports four agent types:

TypeDescriptionContacts Required
aiFully artificial intelligenceNo
humanIndividual personYes
human-orgGroup of people (organization)Yes
hybridHuman-AI combinationYes

AI Agent Example

{
  "$schema": "https://hai.ai/schemas/agent/v1/agent.schema.json",
  "jacsAgentType": "ai",
  "name": "DataBot",
  "description": "Data processing agent",
  "jacsServices": [
    {
      "jacsServiceName": "data-processing",
      "jacsServiceDescription": "Process and transform data"
    }
  ]
}

Human Agent Example

{
  "$schema": "https://hai.ai/schemas/agent/v1/agent.schema.json",
  "jacsAgentType": "human",
  "name": "John Smith",
  "description": "Software engineer",
  "jacsContacts": [
    {
      "jacsContactType": "email",
      "jacsContactValue": "john@example.com"
    }
  ],
  "jacsServices": [
    {
      "jacsServiceName": "code-review",
      "jacsServiceDescription": "Review code for quality and security"
    }
  ]
}

Agent Services

Services define what an agent can do. Each service has:

{
  "jacsServiceName": "service-identifier",
  "jacsServiceDescription": "What the service does",
  "jacsServiceSuccess": "Definition of successful completion",
  "jacsServiceFailure": "What constitutes failure",
  "jacsServiceActions": [
    {
      "jacsActionName": "action-1",
      "jacsActionDescription": "First action in this service"
    }
  ]
}

Service with Actions

{
  "jacsServiceName": "document-processing",
  "jacsServiceDescription": "Process and analyze documents",
  "jacsServiceActions": [
    {
      "jacsActionName": "extract-text",
      "jacsActionDescription": "Extract text from PDF documents"
    },
    {
      "jacsActionName": "summarize",
      "jacsActionDescription": "Generate document summaries"
    },
    {
      "jacsActionName": "translate",
      "jacsActionDescription": "Translate documents between languages"
    }
  ]
}

Agent Contacts

For human and hybrid agents, contacts are required:

{
  "jacsContacts": [
    {
      "jacsContactType": "email",
      "jacsContactValue": "agent@example.com"
    },
    {
      "jacsContactType": "website",
      "jacsContactValue": "https://example.com"
    },
    {
      "jacsContactType": "phone",
      "jacsContactValue": "+1-555-0123"
    }
  ]
}

Cryptographic Keys

Key Algorithms

JACS supports multiple cryptographic algorithms:

AlgorithmDescriptionRecommended For
ring-Ed25519Fast elliptic curve signaturesGeneral use (default)
RSA-PSSTraditional RSA signaturesLegacy compatibility
pq-dilithiumPost-quantum signaturesFuture-proof security

Configure Key Algorithm

In jacs.config.json:

{
  "jacs_agent_key_algorithm": "ring-Ed25519"
}

Or via environment variable:

JACS_AGENT_KEY_ALGORITHM=ring-Ed25519 jacs agent create --create-keys true

Key Storage

Keys are stored in the key directory (default: ./jacs_keys):

jacs_keys/
├── private_key.pem    # Private key (keep secure!)
└── public_key.pem     # Public key (can be shared)

Verifying Agents

Verify Your Own Agent

jacs agent verify

Verify a Specific Agent File

jacs agent verify -a ./path/to/agent.json

With DNS Verification

# Require DNS validation
jacs agent verify --require-dns

# Require strict DNSSEC
jacs agent verify --require-strict-dns

Updating Agents

Agent updates create a new version while maintaining the same jacsId:

  1. Modify the agent document
  2. Re-sign with the agent's keys

The jacsVersion changes but jacsId remains constant.

Agent Document Structure

A complete agent document looks like:

{
  "$schema": "https://hai.ai/schemas/agent/v1/agent.schema.json",
  "jacsId": "550e8400-e29b-41d4-a716-446655440000",
  "jacsVersion": "123e4567-e89b-12d3-a456-426614174000",
  "jacsVersionDate": "2024-01-15T10:30:00Z",
  "jacsOriginalVersion": "123e4567-e89b-12d3-a456-426614174000",
  "jacsOriginalDate": "2024-01-15T10:30:00Z",
  "jacsType": "agent",
  "jacsLevel": "config",

  "jacsAgentType": "ai",
  "jacsAgentDomain": "myagent.example.com",
  "name": "Content Creation Agent",
  "description": "AI agent for content generation",

  "jacsServices": [
    {
      "jacsServiceName": "content-generation",
      "jacsServiceDescription": "Generate high-quality content"
    }
  ],

  "jacsSha256": "hash-of-document",
  "jacsSignature": {
    "agentID": "550e8400-e29b-41d4-a716-446655440000",
    "agentVersion": "123e4567-e89b-12d3-a456-426614174000",
    "signature": "base64-encoded-signature",
    "signingAlgorithm": "ring-Ed25519",
    "publicKeyHash": "hash-of-public-key",
    "date": "2024-01-15T10:30:00Z",
    "fields": ["jacsId", "jacsVersion", "jacsAgentType", "name", "jacsServices"]
  }
}

Best Practices

Security

  1. Protect private keys: Never share or commit private keys
  2. Use strong algorithms: Prefer Ed25519 or post-quantum
  3. Enable DNS verification: For production agents
  4. Regular key rotation: Update keys periodically

Agent Design

  1. Clear service definitions: Be specific about capabilities
  2. Meaningful names: Use descriptive agent names
  3. Contact information: Include for human agents
  4. Version control: Track agent document changes

Operations

  1. Backup keys: Keep secure backups of private keys
  2. Monitor signatures: Watch for unauthorized signing
  3. Document services: Keep service definitions current

Next Steps

Working with Documents

Documents are the core data structure in JACS. Any JSON object can become a JACS document by adding the required header fields and a cryptographic signature.

What is a JACS Document?

A JACS document is a JSON object that includes:

  • Identity: Unique ID and version tracking
  • Metadata: Type, timestamps, and origin information
  • Signature: Cryptographic proof of authenticity
  • Hash: Integrity verification

Creating Documents

From a JSON File

Create a simple JSON document (my-document.json):

{
  "title": "Project Proposal",
  "description": "Q1 development plan",
  "budget": 50000,
  "deadline": "2024-03-31"
}

Sign it with JACS:

jacs document create -f my-document.json

This adds JACS headers and signature, producing a signed document.

From a Directory

Process multiple documents at once:

jacs document create -d ./documents/

With Custom Schema

Validate against a custom JSON schema:

jacs document create -f my-document.json -s ./schemas/proposal.schema.json

Output Options

# Save to specific file
jacs document create -f my-document.json -o ./output/signed-doc.json

# Print to stdout instead of saving
jacs document create -f my-document.json --no-save

# Verbose output
jacs document create -f my-document.json -v

Document Structure

After signing, a document looks like:

{
  "$schema": "https://hai.ai/schemas/header/v1/header.schema.json",
  "jacsId": "doc-uuid-here",
  "jacsVersion": "version-uuid-here",
  "jacsVersionDate": "2024-01-15T10:30:00Z",
  "jacsOriginalVersion": "version-uuid-here",
  "jacsOriginalDate": "2024-01-15T10:30:00Z",
  "jacsType": "document",
  "jacsLevel": "artifact",

  "title": "Project Proposal",
  "description": "Q1 development plan",
  "budget": 50000,
  "deadline": "2024-03-31",

  "jacsSha256": "a1b2c3d4...",
  "jacsSignature": {
    "agentID": "agent-uuid",
    "agentVersion": "agent-version-uuid",
    "signature": "base64-signature",
    "signingAlgorithm": "ring-Ed25519",
    "publicKeyHash": "hash-of-public-key",
    "date": "2024-01-15T10:30:00Z",
    "fields": ["jacsId", "title", "description", "budget", "deadline"]
  }
}

Required Header Fields

FieldDescriptionAuto-generated
$schemaJSON Schema referenceYes
jacsIdPermanent document UUIDYes
jacsVersionVersion UUID (changes on update)Yes
jacsVersionDateWhen this version was createdYes
jacsOriginalVersionFirst version UUIDYes
jacsOriginalDateOriginal creation timestampYes
jacsTypeDocument typeYes
jacsLevelData level (raw, config, artifact, derived)Yes

Document Levels

The jacsLevel field indicates the document's purpose:

LevelDescriptionUse Case
rawOriginal data, should not changeSource documents
configConfiguration, meant to be updatedAgent definitions, settings
artifactGenerated outputReports, summaries
derivedComputed from other documentsAnalysis results

File Attachments

Attach Files

# Attach a single file
jacs document create -f my-document.json --attach ./report.pdf

# Attach a directory of files
jacs document create -f my-document.json --attach ./attachments/

Embed vs. Reference

# Embed files directly in the document (larger document, self-contained)
jacs document create -f my-document.json --attach ./files/ --embed true

# Reference files (smaller document, files stored separately)
jacs document create -f my-document.json --attach ./files/ --embed false

Attachment Structure

Embedded attachments appear in the jacsFiles field:

{
  "jacsFiles": [
    {
      "jacsFileName": "report.pdf",
      "jacsFileMimeType": "application/pdf",
      "jacsFileSha256": "file-hash",
      "jacsFileContent": "base64-encoded-content"
    }
  ]
}

Verifying Documents

Basic Verification

jacs document verify -f ./signed-document.json

Verification checks:

  1. Hash integrity (document hasn't been modified)
  2. Signature validity (signature matches content)
  3. Schema compliance (if schema specified)

Verify with Schema

jacs document verify -f ./document.json -s ./schema.json

Verify Directory

jacs document verify -d ./documents/

Verbose Output

jacs document verify -f ./document.json -v

Updating Documents

Updates create a new version while maintaining the same jacsId:

jacs document update -f ./original.json -n ./modified.json

The update process:

  1. Reads the original document
  2. Applies changes from the modified file
  3. Increments jacsVersion
  4. Links to previous version via jacsPreviousVersion
  5. Re-signs with agent's key

Update with Attachments

jacs document update -f ./original.json -n ./modified.json --attach ./new-file.pdf

Extracting Embedded Content

Extract attachments from a document:

jacs document extract -f ./document-with-attachments.json

Extract from multiple documents:

jacs document extract -d ./documents/

Document Types

Task Documents

Tasks are specialized documents for work tracking:

jacs task create -n "Code Review" -d "Review PR #123"

See Task Schema for details.

Message Documents

Messages for agent communication:

{
  "$schema": "https://hai.ai/schemas/message/v1/message.schema.json",
  "jacsType": "message",
  "jacsMessageContent": "Hello, I've completed the task.",
  "jacsMessageReplyTo": "previous-message-uuid"
}

Custom Documents

Any JSON can be a JACS document. Create custom schemas:

{
  "$schema": "https://example.com/schemas/invoice.schema.json",
  "jacsType": "invoice",
  "invoiceNumber": "INV-001",
  "amount": 1000,
  "currency": "USD"
}

Version History

JACS tracks document history through version chains:

Version 1 (jacsOriginalVersion)
    ↓
Version 2 (jacsPreviousVersion → Version 1)
    ↓
Version 3 (jacsPreviousVersion → Version 2)
    ↓
Current Version

Each version is a complete document that can be independently verified.

Working with Multiple Agents

Different Agent Signs Document

# Use a specific agent's keys
jacs document create -f ./document.json -a ./other-agent.json

Verify Document from Unknown Agent

# Verify with strict DNS requirement
jacs document verify -f ./document.json --require-strict-dns

Best Practices

Document Design

  1. Use appropriate levels: Match jacsLevel to document purpose
  2. Include context: Add descriptive fields for human readability
  3. Version control: Keep source files in git alongside JACS documents

Security

  1. Verify before trusting: Always verify signatures
  2. Check agent identity: Verify the signing agent
  3. Validate schemas: Use custom schemas for strict validation

Performance

  1. External attachments: Use --embed false for large files
  2. Batch processing: Use directory mode for multiple documents
  3. Selective verification: Verify only when needed

Common Workflows

Create and Share Document

# 1. Create document
jacs document create -f ./proposal.json -o ./signed-proposal.json

# 2. Share the signed document
# The recipient can verify it:
jacs document verify -f ./signed-proposal.json

Track Document Changes

# 1. Create initial version
jacs document create -f ./contract-v1.json

# 2. Make changes and update
jacs document update -f ./contract-v1.json -n ./contract-v2.json

# 3. Continue updating
jacs document update -f ./contract-v2.json -n ./contract-v3.json

Process Multiple Documents

# Create all documents in a directory
jacs document create -d ./input-docs/

# Verify all documents
jacs document verify -d ./signed-docs/

Next Steps

Creating and Using Agreements

Agreements enable multi-party consent in JACS. They allow multiple agents to cryptographically sign a document, creating binding commitments between parties.

What is an Agreement?

An agreement is a mechanism for:

  • Collecting signatures from multiple agents
  • Tracking consent from required parties
  • Enforcing completion before proceeding
  • Creating audit trails of who agreed and when

Agreement Lifecycle

1. Create Agreement → 2. Distribute → 3. Agents Sign → 4. Verify Complete
  1. Create: Initial agent creates agreement with required participants
  2. Distribute: Agreement document shared with all parties
  3. Sign: Each agent reviews and adds their signature
  4. Verify: Check that all required parties have signed

Creating Agreements

Basic Agreement

# Create agreement requiring signatures from two agents
jacs document create-agreement \
  -f ./document.json \
  -i agent1-uuid,agent2-uuid

With Context

Include a question and context for clarity:

{
  "jacsAgreement": {
    "jacsAgreementQuestion": "Do you agree to the terms of this contract?",
    "jacsAgreementContext": "Service agreement for Q1 2024",
    "jacsAgreementAgents": ["agent1-uuid", "agent2-uuid"]
  }
}

Signing Agreements

Sign as Current Agent

jacs document sign-agreement -f ./document-with-agreement.json

Sign as Different Agent

# Use a different configuration/agent
JACS_CONFIG_PATH=./agent2.config.json jacs document sign-agreement -f ./document.json

Sign with Response

When signing, agents can include a response:

{
  "jacsAgreement": {
    "signatures": {
      "agent1-uuid": {
        "agentID": "agent1-uuid",
        "signature": "base64-signature",
        "date": "2024-01-15T10:30:00Z",
        "response": "Agreed with minor reservation about timeline",
        "responseType": "agree"
      }
    }
  }
}

Response types:

  • agree - Agent consents
  • disagree - Agent does not consent
  • reject - Agent considers the question invalid or irrelevant

Checking Agreement Status

Check if Complete

jacs document check-agreement -f ./document.json

This shows:

  • Which agents have signed
  • Which agents still need to sign
  • Whether the agreement is complete

Agreement Structure

A document with an agreement includes:

{
  "jacsId": "doc-uuid",
  "jacsType": "contract",

  "jacsAgreement": {
    "jacsAgreementQuestion": "Do you agree to these terms?",
    "jacsAgreementContext": "Annual service contract",
    "jacsAgreementAgents": [
      "550e8400-e29b-41d4-a716-446655440000",
      "123e4567-e89b-12d3-a456-426614174000"
    ],
    "signatures": {
      "550e8400-e29b-41d4-a716-446655440000": {
        "agentID": "550e8400-e29b-41d4-a716-446655440000",
        "agentVersion": "version-uuid",
        "signature": "base64-signature",
        "signingAlgorithm": "ring-Ed25519",
        "publicKeyHash": "hash",
        "date": "2024-01-15T10:30:00Z",
        "responseType": "agree",
        "fields": ["jacsId", "jacsAgreement"]
      }
    }
  },

  "jacsAgreementHash": "hash-of-agreement-content"
}

Task Agreements

Tasks have built-in support for start and end agreements:

{
  "jacsType": "task",
  "jacsTaskName": "Code Review",

  "jacsStartAgreement": {
    "jacsAgreementQuestion": "Do you agree to start this task?",
    "jacsAgreementAgents": ["customer-uuid", "provider-uuid"]
  },

  "jacsEndAgreement": {
    "jacsAgreementQuestion": "Do you agree the task is complete?",
    "jacsAgreementAgents": ["customer-uuid", "provider-uuid"]
  }
}

Multi-Agent Workflow Example

# 1. Agent A creates a task
jacs task create -n "Write Report" -d "Quarterly sales report"

# 2. Agent A adds agreement requiring both agents
jacs document create-agreement \
  -f ./task.json \
  -i agent-a-uuid,agent-b-uuid

# 3. Agent A signs the agreement
jacs document sign-agreement -f ./task.json

# 4. Agent B signs the agreement
JACS_CONFIG_PATH=./agent-b.config.json \
  jacs document sign-agreement -f ./task.json

# 5. Check agreement is complete
jacs document check-agreement -f ./task.json

Agreement Hash

The jacsAgreementHash ensures all agents agree to the same content:

  1. Hash is computed from the agreement content
  2. Each signature includes the hash
  3. If content changes, hash changes, invalidating existing signatures

This prevents modifications after some parties have signed.

Best Practices

  1. Verify before signing: Always review documents before signing
  2. Check agent identities: Verify who you're agreeing with (use DNS)
  3. Include context: Make the agreement purpose clear
  4. Handle disagreement: Have a process for when agents disagree

Next Steps

DNS-Based Agent Verification

JACS supports DNS-based agent verification using DNS TXT records and DNSSEC. This allows agents to publish their identity in a decentralized, verifiable way that doesn't require a central authority.

Overview

DNS verification in JACS works by:

  1. Publishing an agent's public key fingerprint as a DNS TXT record
  2. Using DNSSEC to cryptographically verify the DNS response
  3. Comparing the fingerprint from DNS with the agent's actual public key

This provides a secure, decentralized way to verify agent identity across the internet.

Why DNS Verification?

  • Decentralized: No central authority required
  • Existing Infrastructure: Uses established DNS infrastructure
  • DNSSEC Security: Cryptographic verification of DNS responses
  • Human-Readable: Agents can be identified by domain names
  • Widely Supported: Works with any DNS provider

Publishing Agent Identity

Generate DNS Commands

# Generate DNS TXT record commands for your agent
jacs agent dns --domain myagent.example.com

# Specify agent ID explicitly
jacs agent dns --domain myagent.example.com --agent-id 550e8400-e29b-41d4-a716-446655440000

# Use hex encoding instead of base64
jacs agent dns --domain myagent.example.com --encoding hex

# Set custom TTL (time-to-live)
jacs agent dns --domain myagent.example.com --ttl 7200

Provider-Specific Formats

JACS can generate DNS commands for various providers:

# Plain text format (default)
jacs agent dns --domain myagent.example.com --provider plain

# AWS Route 53 format
jacs agent dns --domain myagent.example.com --provider aws

# Azure DNS format
jacs agent dns --domain myagent.example.com --provider azure

# Cloudflare DNS format
jacs agent dns --domain myagent.example.com --provider cloudflare

DNS Record Structure

The DNS TXT record follows this format:

_v1.agent.jacs.myagent.example.com. 3600 IN TXT "jacs-agent-fingerprint=<fingerprint>"

Where:

  • _v1.agent.jacs. is the JACS-specific subdomain prefix
  • <fingerprint> is the base64-encoded hash of the agent's public key

Setting Up with Route 53 (AWS)

  1. Generate the AWS-formatted command:
jacs agent dns --domain myagent.example.com --provider aws
  1. The output will include an AWS CLI command like:
aws route53 change-resource-record-sets \
  --hosted-zone-id YOUR_ZONE_ID \
  --change-batch '{
    "Changes": [{
      "Action": "UPSERT",
      "ResourceRecordSet": {
        "Name": "_v1.agent.jacs.myagent.example.com",
        "Type": "TXT",
        "TTL": 3600,
        "ResourceRecords": [{"Value": "\"jacs-agent-fingerprint=...\""}]
      }
    }]
  }'
  1. Replace YOUR_ZONE_ID with your actual Route 53 hosted zone ID.

Setting Up with Cloudflare

  1. Generate the Cloudflare-formatted command:
jacs agent dns --domain myagent.example.com --provider cloudflare
  1. Or add manually in the Cloudflare dashboard:
    • Type: TXT
    • Name: _v1.agent.jacs
    • Content: jacs-agent-fingerprint=<your-fingerprint>
    • TTL: 3600

Setting Up with Azure DNS

  1. Generate the Azure-formatted command:
jacs agent dns --domain myagent.example.com --provider azure
  1. The output will include an Azure CLI command that you can run directly.

Verifying Agents with DNS

Look Up Another Agent

# Look up an agent by their domain
jacs agent lookup other-agent.example.com

# With strict DNSSEC validation
jacs agent lookup other-agent.example.com --strict

# Skip DNS verification (not recommended)
jacs agent lookup other-agent.example.com --no-dns

Verify Agent with DNS

When verifying an agent, you can specify DNS requirements:

# Default: Use DNS if available, but don't require it
jacs agent verify -a ./agent.json

# Require DNS validation (non-strict)
jacs agent verify -a ./agent.json --require-dns

# Require strict DNSSEC validation
jacs agent verify -a ./agent.json --require-strict-dns

# Disable DNS validation entirely
jacs agent verify -a ./agent.json --no-dns

# Ignore DNS (won't fail if DNS unavailable)
jacs agent verify -a ./agent.json --ignore-dns

DNS Validation Modes

ModeFlagBehavior
Default(none)Use DNS if available, fall back to local verification
Require DNS--require-dnsFail if DNS record not found (DNSSEC not required)
Require Strict--require-strict-dnsFail if DNSSEC validation fails
No DNS--no-dnsSkip DNS validation entirely
Ignore DNS--ignore-dnsDon't fail on DNS errors, just warn

Agent Domain Configuration

Agents can specify their domain in their agent document:

{
  "jacsId": "550e8400-e29b-41d4-a716-446655440000",
  "jacsAgentType": "ai",
  "jacsAgentDomain": "myagent.example.com",
  "jacsServices": [...]
}

The jacsAgentDomain field is optional but enables DNS-based verification.

DNSSEC Requirements

For maximum security, enable DNSSEC on your domain:

  1. Enable DNSSEC at your registrar: Most registrars support DNSSEC
  2. Configure your DNS provider: Ensure your DNS provider signs zones
  3. Use --require-strict-dns: Enforce DNSSEC validation

Checking DNSSEC Status

You can verify DNSSEC is working using standard tools:

# Check if DNSSEC is enabled
dig +dnssec _v1.agent.jacs.myagent.example.com TXT

# Verify DNSSEC validation
delv @8.8.8.8 _v1.agent.jacs.myagent.example.com TXT

Security Considerations

Trust Model

  • With DNSSEC: Full cryptographic chain of trust from root DNS servers
  • Without DNSSEC: Trust depends on DNS infrastructure security
  • Local Only: Trust is limited to having the correct public key

Best Practices

  1. Always enable DNSSEC for production agents
  2. Use strict validation when verifying unknown agents
  3. Rotate keys carefully - update DNS records before key changes
  4. Monitor DNS records for unauthorized changes
  5. Use short TTLs during transitions then increase for stability

Caching

DNS responses are cached based on TTL. Consider:

  • Short TTL (300-600s): Better for development or key rotation
  • Long TTL (3600-86400s): Better for production stability

Troubleshooting

"DNS record not found"

  1. Verify the record exists:
dig _v1.agent.jacs.myagent.example.com TXT
  1. Check DNS propagation (may take up to 48 hours for new records)

  2. Verify the domain in the agent document matches

"DNSSEC validation failed"

  1. Check DNSSEC is enabled:
dig +dnssec myagent.example.com
  1. Verify DS records at registrar

  2. Use --require-dns instead of --require-strict-dns if DNSSEC isn't available

"Fingerprint mismatch"

  1. The public key may have changed - regenerate DNS record:
jacs agent dns --domain myagent.example.com
  1. Update the DNS TXT record with the new fingerprint

  2. Wait for DNS propagation

Integration with CI/CD

Automate DNS updates in your deployment pipeline:

#!/bin/bash
# deploy-agent.sh

# 1. Create new agent keys
jacs agent create --create-keys true

# 2. Generate DNS update command
DNS_CMD=$(jacs agent dns --domain $AGENT_DOMAIN --provider aws)

# 3. Execute DNS update
eval $DNS_CMD

# 4. Wait for propagation
sleep 60

# 5. Verify DNS is working
jacs agent verify --require-dns

Next Steps

Rust Library API

JACS provides a Rust library for programmatic agent and document management. This chapter covers how to use the JACS library in your Rust applications.

Adding JACS as a Dependency

Add JACS to your Cargo.toml:

[dependencies]
jacs = "0.3"

Feature Flags

[dependencies]
jacs = { version = "0.3", features = ["cli", "observability"] }
FeatureDescription
cliCLI utilities and helpers
observabilityOpenTelemetry logging and metrics
observability-convenienceHelper functions for observability
fullAll features enabled

Core Types

Agent

The Agent struct is the central type in JACS. It holds:

  • Schema validators
  • Agent identity and keys
  • Document storage
  • Configuration
use jacs::{get_empty_agent, load_agent};
use std::error::Error;

fn main() -> Result<(), Box<dyn Error>> {
    // Create a new empty agent
    let agent = get_empty_agent();

    // Or load an existing agent
    let agent = load_agent(Some("path/to/agent.json".to_string()))?;

    Ok(())
}

JACSDocument

Documents in JACS are represented by the JACSDocument struct:

#![allow(unused)]
fn main() {
pub struct JACSDocument {
    pub id: String,
    pub version: String,
    pub value: serde_json::Value,
    pub jacs_type: String,
}
}

Key methods:

  • getkey() - Returns "id:version" identifier
  • getvalue() - Returns reference to the JSON value
  • getschema() - Returns the document's schema URL
  • signing_agent() - Returns the ID of the signing agent

Creating an Agent

Minimal Agent

use jacs::{get_empty_agent, create_minimal_blank_agent};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Create agent JSON
    let agent_json = create_minimal_blank_agent(
        "ai".to_string(),                    // agent type
        Some("My service".to_string()),      // service description
        Some("Task completed".to_string()),  // success description
        Some("Task failed".to_string()),     // failure description
    )?;

    // Initialize and load the agent
    let mut agent = get_empty_agent();
    agent.create_agent_and_load(&agent_json, true, None)?;

    // Save the agent
    agent.save()?;

    Ok(())
}

Loading by Configuration

use jacs::get_empty_agent;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut agent = get_empty_agent();

    // Load from config file
    agent.load_by_config("./jacs.config.json".to_string())?;

    // Or load by agent ID
    agent.load_by_id("agent-id:version-id".to_string())?;

    Ok(())
}

DNS Strict Mode

use jacs::load_agent_with_dns_strict;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Load agent with strict DNS verification
    let agent = load_agent_with_dns_strict(
        "path/to/agent.json".to_string(),
        true  // strict mode
    )?;

    Ok(())
}

Working with Documents

Creating Documents

The DocumentTraits trait provides document operations:

use jacs::agent::document::DocumentTraits;
use jacs::get_empty_agent;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut agent = get_empty_agent();
    agent.load_by_config("./jacs.config.json".to_string())?;

    // Create a document from JSON
    let json = r#"{"title": "My Document", "content": "Hello, World!"}"#;
    let doc = agent.create_document_and_load(json, None, None)?;

    println!("Document created: {}", doc.getkey());

    Ok(())
}

Creating Documents with Attachments

#![allow(unused)]
fn main() {
use jacs::agent::document::DocumentTraits;

// With file attachments
let attachments = Some(vec!["./report.pdf".to_string()]);
let embed = Some(true);  // Embed files in document

let doc = agent.create_document_and_load(
    json,
    attachments,
    embed
)?;
}

Loading Documents

#![allow(unused)]
fn main() {
use jacs::agent::document::DocumentTraits;

// Load a document from JSON string
let doc = agent.load_document(&document_json_string)?;

// Get a stored document by key
let doc = agent.get_document("doc-id:version-id")?;

// List all document keys
let keys = agent.get_document_keys();
}

Updating Documents

#![allow(unused)]
fn main() {
use jacs::agent::document::DocumentTraits;

// Update creates a new version
let updated_doc = agent.update_document(
    "doc-id:version-id",    // original document key
    &modified_json_string,  // new content
    None,                   // optional attachments
    None,                   // embed flag
)?;
}

Verifying Documents

#![allow(unused)]
fn main() {
use jacs::agent::document::DocumentTraits;

// Verify document signature with agent's public key
agent.verify_document_signature(
    "doc-id:version-id",
    None,  // signature key (uses default)
    None,  // fields to verify
    None,  // public key (uses agent's)
    None,  // key encoding type
)?;

// Verify using external public key
agent.verify_external_document_signature("doc-id:version-id")?;
}

Saving Documents

#![allow(unused)]
fn main() {
use jacs::agent::document::DocumentTraits;

// Save document to filesystem
agent.save_document(
    "doc-id:version-id",
    Some("output.json".to_string()),  // output filename
    Some(true),                       // export embedded files
    None,                             // extract only
)?;
}

Creating Tasks

use jacs::{get_empty_agent, create_task};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut agent = get_empty_agent();
    agent.load_by_config("./jacs.config.json".to_string())?;

    // Create a task
    let task_json = create_task(
        &mut agent,
        "Review Code".to_string(),
        "Review pull request #123".to_string(),
    )?;

    println!("Task created: {}", task_json);

    Ok(())
}

Signing and Verification

Signing Documents

The agent's signing_procedure method creates cryptographic signatures:

#![allow(unused)]
fn main() {
use serde_json::json;

let document = json!({
    "title": "Contract",
    "terms": "..."
});

// Sign the document
let signature = agent.signing_procedure(
    &document,
    None,           // fields to sign (None = all)
    "jacsSignature" // placement key
)?;
}

Verification

#![allow(unused)]
fn main() {
// Verify self-signature (agent document)
agent.verify_self_signature()?;

// Verify hash integrity
agent.verify_hash(&document)?;

// Full signature verification
agent.signature_verification_procedure(
    &document,
    None,                    // fields
    "jacsSignature",         // signature key
    public_key,              // public key bytes
    Some("ring-Ed25519".to_string()),  // algorithm
    None,                    // original public key hash
    None,                    // signature override
)?;
}

Custom Schema Validation

#![allow(unused)]
fn main() {
// Load custom schemas
agent.load_custom_schemas(&[
    "./schemas/invoice.schema.json".to_string(),
    "https://example.com/schemas/contract.schema.json".to_string(),
])?;

// Validate document against custom schema
agent.validate_document_with_custom_schema(
    "./schemas/invoice.schema.json",
    &document_value,
)?;
}

Configuration

Loading Configuration

#![allow(unused)]
fn main() {
use jacs::config::{load_config, find_config, Config};

// Load from specific path
let config = load_config("./jacs.config.json")?;

// Find config in directory
let config = find_config("./".to_string())?;

// Create programmatically
let config = Config::new(
    Some("false".to_string()),           // use_security
    Some("./jacs_data".to_string()),     // data_directory
    Some("./jacs_keys".to_string()),     // key_directory
    Some("private_key.pem".to_string()), // private key filename
    Some("public_key.pem".to_string()),  // public key filename
    Some("ring-Ed25519".to_string()),    // key algorithm
    Some("password".to_string()),        // private key password
    None,                                // agent ID and version
    Some("fs".to_string()),              // storage type
);
}

Accessing Configuration

#![allow(unused)]
fn main() {
// Get key algorithm
let algorithm = config.get_key_algorithm()?;

// Access config fields
let data_dir = config.jacs_data_directory();
let key_dir = config.jacs_key_directory();
let storage_type = config.jacs_default_storage();
}

Observability

Initialize Default Observability

use jacs::init_default_observability;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Set up file-based logging
    init_default_observability()?;

    // Your application code...

    Ok(())
}

Custom Observability Configuration

use jacs::{
    init_custom_observability,
    ObservabilityConfig,
    LogConfig,
    LogDestination,
    MetricsConfig,
    MetricsDestination,
};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let config = ObservabilityConfig {
        logs: LogConfig {
            enabled: true,
            level: "debug".to_string(),
            destination: LogDestination::Otlp {
                endpoint: "http://localhost:4317".to_string(),
                headers: None,
            },
            headers: None,
        },
        metrics: MetricsConfig {
            enabled: true,
            destination: MetricsDestination::Prometheus {
                endpoint: "http://localhost:9090".to_string(),
                headers: None,
            },
            export_interval_seconds: Some(30),
            headers: None,
        },
        tracing: None,
    };

    init_custom_observability(config)?;

    Ok(())
}

Storage Backends

JACS supports multiple storage backends:

#![allow(unused)]
fn main() {
use jacs::storage::MultiStorage;

// Filesystem storage (default)
let storage = MultiStorage::new("fs".to_string())?;

// In-memory storage
let storage = MultiStorage::new("memory".to_string())?;

// S3 storage
let storage = MultiStorage::new("s3".to_string())?;
}

Error Handling

JACS functions return Result<T, Box<dyn Error>>:

use jacs::get_empty_agent;

fn main() {
    match get_empty_agent().load_by_config("./jacs.config.json".to_string()) {
        Ok(()) => println!("Agent loaded successfully"),
        Err(e) => eprintln!("Failed to load agent: {}", e),
    }
}

Thread Safety

The Agent struct uses internal mutexes for thread-safe access to:

  • Document schemas (Arc<Mutex<HashMap<String, Validator>>>)
  • Storage operations

For concurrent usage:

#![allow(unused)]
fn main() {
use std::sync::{Arc, Mutex};
use jacs::get_empty_agent;

let agent = Arc::new(Mutex::new(get_empty_agent()));

// Clone Arc for threads
let agent_clone = Arc::clone(&agent);
std::thread::spawn(move || {
    let mut agent = agent_clone.lock().unwrap();
    // Use agent...
});
}

Complete Example

use jacs::{get_empty_agent, create_task};
use jacs::agent::document::DocumentTraits;
use serde_json::json;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Initialize agent
    let mut agent = get_empty_agent();
    agent.load_by_config("./jacs.config.json".to_string())?;

    // Create a document
    let doc_json = json!({
        "title": "Project Proposal",
        "description": "Q1 development plan",
        "budget": 50000
    });

    let doc = agent.create_document_and_load(
        &doc_json.to_string(),
        None,
        None
    )?;

    println!("Created document: {}", doc.getkey());

    // Verify the document
    agent.verify_document_signature(&doc.getkey(), None, None, None, None)?;
    println!("Document verified successfully");

    // Save to file
    agent.save_document(&doc.getkey(), Some("proposal.json".to_string()), None, None)?;

    // Create a task
    let task = create_task(
        &mut agent,
        "Review Proposal".to_string(),
        "Review and approve the project proposal".to_string(),
    )?;

    println!("Task created");

    Ok(())
}

Next Steps

Observability

JACS provides comprehensive observability features including logging, metrics, and distributed tracing. This chapter covers configuring and using these features in your applications.

Overview

JACS observability is built on the OpenTelemetry standard, providing:

  • Logging: Structured logging with multiple destinations
  • Metrics: Counters, gauges, and histograms for monitoring
  • Tracing: Distributed tracing for request flows

Feature Flags

Enable observability features in your Cargo.toml:

[dependencies]
jacs = { version = "0.3", features = ["observability"] }
FeatureDescription
observabilityCore observability support
observability-convenienceHelper functions for recording operations
otlp-logsOTLP log export support
otlp-metricsOTLP metrics export support
otlp-tracingOTLP distributed tracing support

Quick Start

Default Configuration

The simplest way to enable observability:

use jacs::init_default_observability;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    init_default_observability()?;

    // Your application code...

    Ok(())
}

This sets up:

  • File-based logging to ./logs/ with daily rotation
  • Metrics disabled by default
  • Tracing disabled by default

Custom Configuration

For more control, use init_custom_observability:

use jacs::{
    init_custom_observability,
    ObservabilityConfig,
    LogConfig,
    LogDestination,
    MetricsConfig,
    MetricsDestination,
};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let config = ObservabilityConfig {
        logs: LogConfig {
            enabled: true,
            level: "info".to_string(),
            destination: LogDestination::Stderr,
            headers: None,
        },
        metrics: MetricsConfig {
            enabled: false,
            destination: MetricsDestination::Stdout,
            export_interval_seconds: None,
            headers: None,
        },
        tracing: None,
    };

    init_custom_observability(config)?;
    Ok(())
}

Logging

Log Levels

Supported log levels (from most to least verbose):

  • trace
  • debug
  • info
  • warn
  • error

Log Destinations

Stderr (Default)

#![allow(unused)]
fn main() {
LogDestination::Stderr
}

Logs to standard error. Useful for development and containerized environments.

File

#![allow(unused)]
fn main() {
LogDestination::File {
    path: "./logs".to_string(),
}
}

Logs to rotating files with daily rotation. Creates files like app.log.2024-01-15.

OTLP

#![allow(unused)]
fn main() {
LogDestination::Otlp {
    endpoint: "http://localhost:4318".to_string(),
    headers: None,
}
}

Exports logs via OpenTelemetry Protocol. Requires otlp-logs feature.

Null

#![allow(unused)]
fn main() {
LogDestination::Null
}

Disables logging completely.

Using Logs

JACS uses the tracing crate for logging:

#![allow(unused)]
fn main() {
use tracing::{info, debug, warn, error};

fn process_document() {
    info!("Processing document");
    debug!("Document details: {:?}", doc);

    if let Err(e) = verify() {
        error!("Verification failed: {}", e);
    }
}
}

Metrics

Enabling Metrics

#![allow(unused)]
fn main() {
MetricsConfig {
    enabled: true,
    destination: MetricsDestination::Otlp {
        endpoint: "http://localhost:4318".to_string(),
        headers: None,
    },
    export_interval_seconds: Some(30),
    headers: None,
}
}

Metrics Destinations

OTLP

#![allow(unused)]
fn main() {
MetricsDestination::Otlp {
    endpoint: "http://localhost:4318".to_string(),
    headers: None,
}
}

Exports to an OpenTelemetry collector. Requires otlp-metrics feature.

Prometheus (via Collector)

#![allow(unused)]
fn main() {
MetricsDestination::Prometheus {
    endpoint: "http://localhost:9090".to_string(),
    headers: None,
}
}

Note: Direct Prometheus export requires routing through an OTLP collector.

File

#![allow(unused)]
fn main() {
MetricsDestination::File {
    path: "./metrics.txt".to_string(),
}
}

Writes metrics to a file.

Stdout

#![allow(unused)]
fn main() {
MetricsDestination::Stdout
}

Prints metrics to standard output. Useful for testing.

Recording Metrics

JACS provides convenience functions for common metrics:

#![allow(unused)]
fn main() {
use jacs::observability::metrics::{increment_counter, set_gauge, record_histogram};
use std::collections::HashMap;

// Increment a counter
let mut tags = HashMap::new();
tags.insert("operation".to_string(), "sign".to_string());
increment_counter("jacs_operations_total", 1, Some(tags));

// Set a gauge value
set_gauge("jacs_documents_active", 42.0, None);

// Record a histogram value (e.g., latency)
let mut tags = HashMap::new();
tags.insert("method".to_string(), "verify".to_string());
record_histogram("jacs_operation_duration_ms", 150.0, Some(tags));
}

Built-in Metrics

When observability-convenience feature is enabled, JACS automatically records:

  • jacs_agent_operations - Count of agent operations
  • jacs_signature_verifications - Signature verification results
  • jacs_document_operations - Document create/update/verify counts

Distributed Tracing

Enabling Tracing

#![allow(unused)]
fn main() {
use jacs::{TracingConfig, TracingDestination, SamplingConfig, ResourceConfig};
use std::collections::HashMap;

let config = ObservabilityConfig {
    // ... logs and metrics config ...
    tracing: Some(TracingConfig {
        enabled: true,
        sampling: SamplingConfig {
            ratio: 1.0,           // Sample all traces
            parent_based: true,
            rate_limit: None,
        },
        resource: Some(ResourceConfig {
            service_name: "my-jacs-app".to_string(),
            service_version: Some("1.0.0".to_string()),
            environment: Some("production".to_string()),
            attributes: HashMap::new(),
        }),
        destination: Some(TracingDestination::Otlp {
            endpoint: "http://localhost:4318".to_string(),
            headers: None,
        }),
    }),
};
}

Tracing Destinations

OTLP

#![allow(unused)]
fn main() {
TracingDestination::Otlp {
    endpoint: "http://localhost:4318".to_string(),
    headers: None,
}
}

Jaeger

#![allow(unused)]
fn main() {
TracingDestination::Jaeger {
    endpoint: "http://localhost:14268/api/traces".to_string(),
    headers: None,
}
}

Sampling Configuration

Control how many traces are captured:

#![allow(unused)]
fn main() {
SamplingConfig {
    ratio: 0.1,          // Sample 10% of traces
    parent_based: true,  // Inherit parent sampling decision
    rate_limit: Some(100), // Max 100 samples per second
}
}

Using Tracing Spans

#![allow(unused)]
fn main() {
use tracing::{instrument, info_span};

#[instrument]
fn sign_document(doc: &Document) -> Result<(), Error> {
    // Automatically creates a span named "sign_document"
    // with doc as a field
}

fn manual_span() {
    let span = info_span!("verify_chain", doc_count = 5);
    let _guard = span.enter();

    // Operations within this span
}
}

Configuration File

You can configure observability via jacs.config.json:

{
  "$schema": "https://hai.ai/schemas/jacs.config.schema.json",
  "jacs_data_directory": "./jacs_data",
  "jacs_key_directory": "./jacs_keys",
  "jacs_agent_key_algorithm": "ring-Ed25519",
  "observability": {
    "logs": {
      "enabled": true,
      "level": "info",
      "destination": {
        "file": {
          "path": "./logs"
        }
      }
    },
    "metrics": {
      "enabled": true,
      "destination": {
        "otlp": {
          "endpoint": "http://localhost:4318"
        }
      },
      "export_interval_seconds": 30
    },
    "tracing": {
      "enabled": true,
      "sampling": {
        "ratio": 1.0,
        "parent_based": true
      },
      "resource": {
        "service_name": "jacs-service",
        "service_version": "1.0.0",
        "environment": "production"
      },
      "destination": {
        "otlp": {
          "endpoint": "http://localhost:4318"
        }
      }
    }
  }
}

OpenTelemetry Collector Setup

For production use, route telemetry through an OpenTelemetry Collector:

# otel-collector-config.yaml
receivers:
  otlp:
    protocols:
      http:
        endpoint: 0.0.0.0:4318

processors:
  batch:

exporters:
  logging:
    loglevel: debug
  prometheus:
    endpoint: "0.0.0.0:9090"
  jaeger:
    endpoint: jaeger:14250

service:
  pipelines:
    logs:
      receivers: [otlp]
      processors: [batch]
      exporters: [logging]
    metrics:
      receivers: [otlp]
      processors: [batch]
      exporters: [prometheus]
    traces:
      receivers: [otlp]
      processors: [batch]
      exporters: [jaeger]

Reset and Cleanup

For testing or reinitialization:

#![allow(unused)]
fn main() {
use jacs::observability::{reset_observability, flush_observability, force_reset_for_tests};

// Flush pending data
flush_observability();

// Reset configuration
reset_observability();

// Force reset for tests (clears all state)
force_reset_for_tests();
}

Best Practices

Development

#![allow(unused)]
fn main() {
let config = ObservabilityConfig {
    logs: LogConfig {
        enabled: true,
        level: "debug".to_string(),
        destination: LogDestination::Stderr,
        headers: None,
    },
    metrics: MetricsConfig {
        enabled: false,
        destination: MetricsDestination::Stdout,
        export_interval_seconds: None,
        headers: None,
    },
    tracing: None,
};
}

Production

#![allow(unused)]
fn main() {
let config = ObservabilityConfig {
    logs: LogConfig {
        enabled: true,
        level: "info".to_string(),
        destination: LogDestination::Otlp {
            endpoint: "http://collector:4318".to_string(),
            headers: Some(auth_headers()),
        },
        headers: None,
    },
    metrics: MetricsConfig {
        enabled: true,
        destination: MetricsDestination::Otlp {
            endpoint: "http://collector:4318".to_string(),
            headers: Some(auth_headers()),
        },
        export_interval_seconds: Some(30),
        headers: None,
    },
    tracing: Some(TracingConfig {
        enabled: true,
        sampling: SamplingConfig {
            ratio: 0.1,  // Sample 10% in production
            parent_based: true,
            rate_limit: Some(1000),
        },
        resource: Some(ResourceConfig {
            service_name: "jacs-production".to_string(),
            service_version: Some(env!("CARGO_PKG_VERSION").to_string()),
            environment: Some("production".to_string()),
            attributes: HashMap::new(),
        }),
        destination: Some(TracingDestination::Otlp {
            endpoint: "http://collector:4318".to_string(),
            headers: Some(auth_headers()),
        }),
    }),
};
}

Troubleshooting

Logs Not Appearing

  1. Check that logging is enabled: logs.enabled: true
  2. Verify log level includes your log statements
  3. For file logging, ensure the directory is writable

Metrics Not Exporting

  1. Verify otlp-metrics feature is enabled
  2. Check endpoint connectivity
  3. Confirm metrics are enabled: metrics.enabled: true

Traces Missing

  1. Verify otlp-tracing feature is enabled
  2. Check sampling ratio isn't filtering all traces
  3. Ensure spans are properly instrumented

Next Steps

Node.js Installation

The JACS Node.js package (jacsnpm) provides JavaScript/TypeScript bindings to the JACS Rust library, making it easy to integrate JACS into web applications, servers, and Node.js projects.

Requirements

  • Node.js: Version 16.0 or higher
  • npm or yarn: For package management
  • Operating System: macOS, Linux, or Windows with WSL

Installation

Using npm

npm install jacsnpm

Using yarn

yarn add jacsnpm

Using pnpm

pnpm add jacsnpm

Verify Installation

Create a simple test to verify everything is working:

// test.js
import { JacsAgent } from 'jacsnpm';

console.log('JACS Node.js bindings loaded successfully!');

// Test basic functionality
try {
  const agent = new JacsAgent();
  agent.load('./jacs.config.json');
  console.log('Agent loaded successfully!');
} catch (error) {
  console.error('Error loading agent:', error);
}

Run the test:

node test.js

Package Structure

The jacsnpm package includes several modules:

Core Module (jacsnpm)

import { 
  JacsAgent,
  JacsConfig,
  JacsDocument,
  JacsError
} from 'jacsnpm';

MCP Integration (jacsnpm/mcp)

import { 
  JacsMcpServer,
  createJacsMiddleware 
} from 'jacsnpm/mcp';

HTTP Server (jacsnpm/http)

import { 
  JacsHttpServer,
  createJacsRouter 
} from 'jacsnpm/http';

TypeScript Support

The package includes full TypeScript definitions:

import { JacsAgent, createConfig, hashString } from 'jacsnpm';

// Create an agent instance
const agent: JacsAgent = new JacsAgent();

// Load configuration from file
agent.load('./jacs.config.json');

// Use utility functions
const hash: string = hashString('some data');

// Create a configuration string
const configJson: string = createConfig(
  undefined,           // jacs_use_security
  './jacs_data',       // jacs_data_directory
  './jacs_keys',       // jacs_key_directory
  undefined,           // jacs_agent_private_key_filename
  undefined,           // jacs_agent_public_key_filename
  'ring-Ed25519',      // jacs_agent_key_algorithm
  undefined,           // jacs_private_key_password
  undefined,           // jacs_agent_id_and_version
  'fs'                 // jacs_default_storage
);

Configuration

Basic Configuration

const config = {
  // Required fields
  jacs_data_directory: "./jacs_data",      // Where documents are stored
  jacs_key_directory: "./jacs_keys",       // Where keys are stored
  jacs_default_storage: "fs",              // Storage backend
  jacs_agent_key_algorithm: "ring-Ed25519",     // Signing algorithm
  
  // Optional fields
  jacs_agent_id_and_version: null,         // Existing agent to load
  jacs_agent_private_key_filename: "private.pem",
  jacs_agent_public_key_filename: "public.pem"
};

Configuration File

Create a jacs.config.json file:

{
  "$schema": "https://hai.ai/schemas/jacs.config.schema.json",
  "jacs_data_directory": "./jacs_data",
  "jacs_key_directory": "./jacs_keys",
  "jacs_default_storage": "fs",
  "jacs_agent_key_algorithm": "ring-Ed25519"
}

Load the configuration:

import { JacsAgent } from 'jacsnpm';

const agent = new JacsAgent();
agent.load('./jacs.config.json');

Environment Variables

JACS reads environment variables that override configuration file settings:

export JACS_DATA_DIRECTORY="./production_data"
export JACS_KEY_DIRECTORY="./production_keys"
export JACS_AGENT_KEY_ALGORITHM="ring-Ed25519"
export JACS_DEFAULT_STORAGE="fs"

Storage Backends

Configure storage in jacs.config.json:

File System (Default)

{
  "jacs_default_storage": "fs",
  "jacs_data_directory": "./jacs_data",
  "jacs_key_directory": "./jacs_keys"
}

S3 Storage

{
  "jacs_default_storage": "s3"
}

S3 credentials are read from standard AWS environment variables.

Memory Storage (Testing)

{
  "jacs_default_storage": "memory"
}

Cryptographic Algorithms

{
  "jacs_agent_key_algorithm": "ring-Ed25519"
}

Pros: Fast, secure, small signatures Cons: Requires elliptic curve support

RSA-PSS

{
  "jacs_agent_key_algorithm": "RSA-PSS"
}

Pros: Widely supported, proven security Cons: Larger signatures, slower

pq-dilithium (Post-Quantum)

{
  "jacs_agent_key_algorithm": "pq-dilithium"
}

Pros: Quantum-resistant Cons: Experimental, large signatures

pq2025 (Post-Quantum Hybrid)

{
  "jacs_agent_key_algorithm": "pq2025"
}

Pros: Combines ML-DSA-87 with hybrid approach Cons: Newest algorithm, largest signatures

Development Setup

Project Structure

my-jacs-project/
├── package.json
├── jacs.config.json
├── src/
│   ├── agent.js
│   ├── tasks.js
│   └── agreements.js
├── jacs_data/
│   ├── agents/
│   ├── tasks/
│   └── documents/
└── jacs_keys/
    ├── private.pem
    └── public.pem

Package.json Setup

{
  "name": "my-jacs-app",
  "version": "1.0.0",
  "type": "module",
  "dependencies": {
    "jacsnpm": "^0.1.0",
    "express": "^4.18.0"
  },
  "scripts": {
    "start": "node src/app.js",
    "test": "node test/test.js",
    "dev": "nodemon src/app.js"
  }
}

Basic Application

// src/app.js
import { JacsAgent } from 'jacsnpm';

// Create and load agent
const agent = new JacsAgent();
agent.load('./jacs.config.json');

// Create a document
const documentJson = JSON.stringify({
  title: "My First Document",
  content: "Hello from Node.js!"
});

const signedDoc = agent.createDocument(documentJson);
console.log('Document created:', signedDoc);

// Verify the document
const isValid = agent.verifyDocument(signedDoc);
console.log('Document valid:', isValid);

console.log('JACS agent ready!');

Common Issues

Module Not Found

If you get Module not found errors:

# Check Node.js version
node --version  # Should be 16+

# Clear node_modules and reinstall
rm -rf node_modules package-lock.json
npm install

Permission Errors

If you get permission errors accessing files:

# Check directory permissions
ls -la jacs_data/ jacs_keys/

# Fix permissions
chmod 755 jacs_data/ jacs_keys/
chmod 600 jacs_keys/*.pem

Binary Compatibility

If you get binary compatibility errors:

# Rebuild native modules
npm rebuild

# Or reinstall
npm uninstall jacsnpm
npm install jacsnpm

TypeScript Issues

If TypeScript can't find definitions:

// tsconfig.json
{
  "compilerOptions": {
    "moduleResolution": "node",
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true
  }
}

Next Steps

Now that you have JACS installed:

  1. Basic Usage - Learn core JACS operations
  2. MCP Integration - Add Model Context Protocol support
  3. HTTP Server - Create JACS HTTP APIs
  4. Express Middleware - Integrate with Express.js
  5. API Reference - Complete API documentation

Examples

Check out the complete examples in the examples directory:

  • Basic agent creation and task management
  • Express.js middleware integration
  • MCP server implementation

Basic Usage

This chapter covers fundamental JACS operations in Node.js, including agent initialization, document creation, signing, and verification.

Initializing an Agent

Create and Load Agent

import { JacsAgent } from 'jacsnpm';

// Create a new agent instance
const agent = new JacsAgent();

// Load configuration from file
agent.load('./jacs.config.json');

Configuration File

Create jacs.config.json:

{
  "$schema": "https://hai.ai/schemas/jacs.config.schema.json",
  "jacs_data_directory": "./jacs_data",
  "jacs_key_directory": "./jacs_keys",
  "jacs_default_storage": "fs",
  "jacs_agent_key_algorithm": "ring-Ed25519",
  "jacs_agent_id_and_version": "agent-uuid:version-uuid"
}

Creating Documents

Basic Document Creation

import { JacsAgent } from 'jacsnpm';

const agent = new JacsAgent();
agent.load('./jacs.config.json');

// Create a document from JSON
const documentData = {
  title: "Project Proposal",
  content: "Quarterly development plan",
  budget: 50000
};

const signedDocument = agent.createDocument(JSON.stringify(documentData));
console.log('Signed document:', signedDocument);

With Custom Schema

Validate against a custom JSON Schema:

const signedDocument = agent.createDocument(
  JSON.stringify(documentData),
  './schemas/proposal.schema.json'  // custom schema path
);

With Output File

const signedDocument = agent.createDocument(
  JSON.stringify(documentData),
  null,                    // no custom schema
  './output/proposal.json' // output filename
);

Without Saving

const signedDocument = agent.createDocument(
  JSON.stringify(documentData),
  null,   // no custom schema
  null,   // no output filename
  true    // noSave = true
);

With Attachments

const signedDocument = agent.createDocument(
  JSON.stringify(documentData),
  null,                      // no custom schema
  null,                      // no output filename
  false,                     // save the document
  './attachments/report.pdf', // attachment path
  true                       // embed files
);

Verifying Documents

Verify Document Signature

// Verify a document's signature and hash
const isValid = agent.verifyDocument(signedDocumentJson);
console.log('Document valid:', isValid);

Verify Specific Signature Field

// Verify with a custom signature field
const isValid = agent.verifySignature(
  signedDocumentJson,
  'jacsSignature'  // signature field name
);

Updating Documents

Update Existing Document

// Original document key format: "id:version"
const documentKey = 'doc-uuid:version-uuid';

// Modified document content
const updatedData = {
  jacsId: 'doc-uuid',
  jacsVersion: 'version-uuid',
  title: "Updated Proposal",
  content: "Revised quarterly plan",
  budget: 75000
};

const updatedDocument = agent.updateDocument(
  documentKey,
  JSON.stringify(updatedData)
);

console.log('Updated document:', updatedDocument);

Update with New Attachments

const updatedDocument = agent.updateDocument(
  documentKey,
  JSON.stringify(updatedData),
  ['./new-report.pdf'],  // new attachments
  true                   // embed files
);

Signing and Verification

Sign Arbitrary Data

// Sign any string data
const signature = agent.signString('Important message to sign');
console.log('Signature:', signature);

Verify Arbitrary Data

// Verify a signature on string data
const isValid = agent.verifyString(
  'Important message to sign',  // original data
  signatureBase64,              // base64 signature
  publicKeyBuffer,              // public key as Buffer
  'ring-Ed25519'                // algorithm
);

Working with Agreements

Create an Agreement

// Add agreement requiring multiple agent signatures
const documentWithAgreement = agent.createAgreement(
  signedDocumentJson,
  ['agent1-uuid', 'agent2-uuid'],           // required signers
  'Do you agree to these terms?',            // question
  'Q1 2024 service contract',                // context
  'jacsAgreement'                            // field name
);

Sign an Agreement

// Sign the agreement as the current agent
const signedAgreement = agent.signAgreement(
  documentWithAgreementJson,
  'jacsAgreement'  // agreement field name
);

Check Agreement Status

// Check which agents have signed
const status = agent.checkAgreement(
  documentWithAgreementJson,
  'jacsAgreement'
);

console.log('Agreement status:', JSON.parse(status));

Agent Operations

Verify Agent

// Verify the loaded agent's signature
const isValid = agent.verifyAgent();
console.log('Agent valid:', isValid);

// Verify a specific agent file
const isValidOther = agent.verifyAgent('./other-agent.json');

Update Agent

// Update agent document
const updatedAgentJson = agent.updateAgent(JSON.stringify({
  jacsId: 'agent-uuid',
  jacsVersion: 'version-uuid',
  name: 'Updated Agent Name',
  description: 'Updated description'
}));

Sign External Agent

// Sign another agent's document with registration signature
const signedAgentJson = agent.signAgent(
  externalAgentJson,
  publicKeyBuffer,
  'ring-Ed25519'
);

Request/Response Signing

Sign a Request

// Sign request parameters as a JACS document
const signedRequest = agent.signRequest({
  method: 'GET',
  path: '/api/resource',
  timestamp: new Date().toISOString(),
  body: { query: 'data' }
});

Verify a Response

// Verify a signed response
const result = agent.verifyResponse(signedResponseJson);
console.log('Response valid:', result);

// Verify and get signer's agent ID
const resultWithId = agent.verifyResponseWithAgentId(signedResponseJson);
console.log('Signer ID:', resultWithId);

Utility Functions

Hash String

import { hashString } from 'jacsnpm';

// SHA-256 hash of a string
const hash = hashString('data to hash');
console.log('Hash:', hash);

Create Configuration

import { createConfig } from 'jacsnpm';

// Programmatically create a config JSON string
const configJson = createConfig(
  undefined,            // jacs_use_security
  './jacs_data',        // jacs_data_directory
  './jacs_keys',        // jacs_key_directory
  undefined,            // private key filename
  undefined,            // public key filename
  'ring-Ed25519',       // key algorithm
  undefined,            // private key password
  undefined,            // agent id and version
  'fs'                  // default storage
);

console.log('Config:', configJson);

Error Handling

import { JacsAgent } from 'jacsnpm';

const agent = new JacsAgent();

try {
  agent.load('./jacs.config.json');
} catch (error) {
  console.error('Failed to load agent:', error.message);
}

try {
  const doc = agent.createDocument(JSON.stringify({ data: 'test' }));
  console.log('Document created');
} catch (error) {
  console.error('Failed to create document:', error.message);
}

try {
  const isValid = agent.verifyDocument(invalidJson);
} catch (error) {
  console.error('Verification failed:', error.message);
}

Complete Example

import { JacsAgent, hashString } from 'jacsnpm';

async function main() {
  // Initialize agent
  const agent = new JacsAgent();
  agent.load('./jacs.config.json');

  // Create a task document
  const task = {
    title: 'Code Review',
    description: 'Review pull request #123',
    assignee: 'developer-uuid',
    deadline: '2024-02-01'
  };

  const signedTask = agent.createDocument(JSON.stringify(task));
  console.log('Task created');

  // Verify the task
  if (agent.verifyDocument(signedTask)) {
    console.log('Task signature valid');
  }

  // Create agreement for task acceptance
  const taskWithAgreement = agent.createAgreement(
    signedTask,
    ['manager-uuid', 'developer-uuid'],
    'Do you accept this task assignment?'
  );

  // Sign the agreement
  const signedAgreement = agent.signAgreement(taskWithAgreement);
  console.log('Agreement signed');

  // Check agreement status
  const status = agent.checkAgreement(signedAgreement);
  console.log('Status:', status);

  // Hash some data for reference
  const taskHash = hashString(signedTask);
  console.log('Task hash:', taskHash);
}

main().catch(console.error);

Next Steps

Model Context Protocol (MCP) Integration

JACS provides native integration with the Model Context Protocol (MCP), enabling secure agent communication within AI systems. JACS uses a transport proxy pattern that wraps any MCP transport with cryptographic signing and verification.

What is MCP?

Model Context Protocol is a standard for AI models to securely access external tools, data, and services. JACS enhances MCP by adding:

  • Cryptographic verification of all messages
  • Agent identity for all operations
  • Transparent encryption of MCP JSON-RPC traffic
  • Audit trails of all MCP interactions

How JACS MCP Works

JACS provides a transport proxy that sits between your MCP server/client and the underlying transport (STDIO, SSE, WebSocket). The proxy:

  1. Outgoing messages: Signs JSON-RPC messages with the JACS agent's key using signRequest()
  2. Incoming messages: Verifies signatures using verifyResponse() and extracts the payload
  3. Fallback: If verification fails, passes messages through as plain JSON (graceful degradation)

Quick Start

Basic MCP Server with JACS

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { createJACSTransportProxy } from 'jacsnpm/mcp';
import { z } from 'zod';

const JACS_CONFIG_PATH = "./jacs.config.json";

async function main() {
  // Create the base STDIO transport
  const baseTransport = new StdioServerTransport();

  // Wrap with JACS encryption
  const secureTransport = createJACSTransportProxy(
    baseTransport,
    JACS_CONFIG_PATH,
    "server"
  );

  // Create MCP server
  const server = new McpServer({
    name: "my-jacs-server",
    version: "1.0.0"
  });

  // Register tools
  server.tool("add", {
    a: z.number().describe("First number"),
    b: z.number().describe("Second number")
  }, async ({ a, b }) => {
    return { content: [{ type: "text", text: `${a} + ${b} = ${a + b}` }] };
  });

  // Connect with JACS encryption
  await server.connect(secureTransport);
  console.error("JACS MCP Server running with encryption enabled");
}

main();

MCP Client with JACS

import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
import { createJACSTransportProxy } from 'jacsnpm/mcp';

const JACS_CONFIG_PATH = "./jacs.config.json";

async function main() {
  // Create base transport to connect to MCP server
  const baseTransport = new StdioClientTransport({
    command: 'node',
    args: ['my-jacs-server.js']
  });

  // Wrap with JACS encryption
  const secureTransport = createJACSTransportProxy(
    baseTransport,
    JACS_CONFIG_PATH,
    "client"
  );

  // Create MCP client
  const client = new Client({
    name: "my-jacs-client",
    version: "1.0.0"
  }, {
    capabilities: {
      tools: {}
    }
  });

  // Connect with JACS encryption
  await client.connect(secureTransport);

  // List available tools
  const tools = await client.listTools();
  console.log('Available tools:', tools.tools.map(t => t.name));

  // Call a tool (message will be JACS-signed)
  const result = await client.callTool({
    name: "add",
    arguments: { a: 5, b: 3 }
  });

  console.log('Result:', result.content);
}

main();

API Reference

JACSTransportProxy

The main class that wraps MCP transports with JACS encryption.

import { JACSTransportProxy } from 'jacsnpm/mcp';

const proxy = new JACSTransportProxy(
  transport,      // Any MCP transport (Stdio, SSE, WebSocket)
  role,           // "server" or "client"
  jacsConfigPath  // Path to jacs.config.json
);

createJACSTransportProxy

Factory function for creating a transport proxy.

import { createJACSTransportProxy } from 'jacsnpm/mcp';

const secureTransport = createJACSTransportProxy(
  baseTransport,    // The underlying MCP transport
  configPath,       // Path to jacs.config.json
  role              // "server" or "client"
);

createJACSTransportProxyAsync

Async factory that waits for JACS to be fully loaded before returning.

import { createJACSTransportProxyAsync } from 'jacsnpm/mcp';

const secureTransport = await createJACSTransportProxyAsync(
  baseTransport,
  configPath,
  role
);

Transport Options

STDIO Transport

Best for CLI tools and subprocess communication:

import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { createJACSTransportProxy } from 'jacsnpm/mcp';

const baseTransport = new StdioServerTransport();
const secureTransport = createJACSTransportProxy(
  baseTransport,
  "./jacs.config.json",
  "server"
);

Important: When using STDIO transport, all debug logging goes to stderr to keep stdout clean for JSON-RPC messages.

SSE Transport (HTTP)

For web-based MCP servers:

import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
import { createJACSTransportProxy } from 'jacsnpm/mcp';
import express from 'express';

const app = express();

app.get('/sse', (req, res) => {
  const baseTransport = new SSEServerTransport('/messages', res);
  const secureTransport = createJACSTransportProxy(
    baseTransport,
    "./jacs.config.json",
    "server"
  );

  // Connect your MCP server to secureTransport
  server.connect(secureTransport);
});

// Handle POST messages with JACS decryption
app.post('/messages', express.text(), async (req, res) => {
  await secureTransport.handlePostMessage(req, res, req.body);
});

app.listen(3000);

Configuration

JACS Config File

Create a jacs.config.json for your MCP server/client:

{
  "$schema": "https://hai.ai/schemas/jacs.config.schema.json",
  "jacs_data_directory": "./jacs_data",
  "jacs_key_directory": "./jacs_keys",
  "jacs_default_storage": "fs",
  "jacs_agent_key_algorithm": "ring-Ed25519",
  "jacs_agent_id_and_version": "agent-uuid:version-uuid"
}

Environment Variables

Enable debug logging (not recommended for STDIO):

export JACS_MCP_DEBUG=true

How Messages Are Signed

Outgoing Messages

When the MCP SDK sends a message, the proxy intercepts it and:

  1. Serializes the JSON-RPC message
  2. Calls jacs.signRequest(message) to create a JACS artifact
  3. Sends the signed artifact to the transport
// Original MCP message
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": { "name": "add", "arguments": { "a": 5, "b": 3 } }
}

// Becomes a JACS-signed artifact with jacsId, jacsSignature, etc.

Incoming Messages

When the transport receives a message, the proxy:

  1. Attempts to verify it as a JACS artifact using jacs.verifyResponse()
  2. If valid, extracts the original JSON-RPC payload
  3. If not valid JACS, parses as plain JSON (fallback mode)
  4. Passes the clean message to the MCP SDK

Complete Example

Server (mcp.server.js)

#!/usr/bin/env node
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { createJACSTransportProxy } from 'jacsnpm/mcp';
import { z } from 'zod';

async function main() {
  console.error("JACS MCP Server starting...");

  // Create transport with JACS encryption
  const baseTransport = new StdioServerTransport();
  const secureTransport = createJACSTransportProxy(
    baseTransport,
    "./jacs.server.config.json",
    "server"
  );

  // Create MCP server
  const server = new McpServer({
    name: "jacs-demo-server",
    version: "1.0.0"
  });

  // Register tools
  server.tool("echo", {
    message: z.string().describe("Message to echo")
  }, async ({ message }) => {
    console.error(`Echo called with: ${message}`);
    return { content: [{ type: "text", text: `Echo: ${message}` }] };
  });

  server.tool("add", {
    a: z.number().describe("First number"),
    b: z.number().describe("Second number")
  }, async ({ a, b }) => {
    console.error(`Add called with: ${a}, ${b}`);
    return { content: [{ type: "text", text: `Result: ${a + b}` }] };
  });

  // Register resources
  server.resource(
    "server-info",
    "info://server",
    async (uri) => ({
      contents: [{
        uri: uri.href,
        text: "JACS-secured MCP Server",
        mimeType: "text/plain"
      }]
    })
  );

  // Connect
  await server.connect(secureTransport);
  console.error("Server running with JACS encryption");
}

main().catch(err => {
  console.error("Fatal error:", err);
  process.exit(1);
});

Client (mcp.client.js)

#!/usr/bin/env node
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
import { createJACSTransportProxy } from 'jacsnpm/mcp';

async function main() {
  console.log("JACS MCP Client starting...");

  // Connect to the server
  const baseTransport = new StdioClientTransport({
    command: 'node',
    args: ['mcp.server.js']
  });

  const secureTransport = createJACSTransportProxy(
    baseTransport,
    "./jacs.client.config.json",
    "client"
  );

  const client = new Client({
    name: "jacs-demo-client",
    version: "1.0.0"
  }, {
    capabilities: { tools: {} }
  });

  await client.connect(secureTransport);
  console.log("Connected to JACS MCP Server");

  // List tools
  const tools = await client.listTools();
  console.log("Available tools:", tools.tools.map(t => t.name));

  // Call echo tool
  const echoResult = await client.callTool({
    name: "echo",
    arguments: { message: "Hello, JACS!" }
  });
  console.log("Echo result:", echoResult.content[0].text);

  // Call add tool
  const addResult = await client.callTool({
    name: "add",
    arguments: { a: 10, b: 20 }
  });
  console.log("Add result:", addResult.content[0].text);

  await client.close();
}

main().catch(console.error);

Security Considerations

Message Verification

All JACS-signed messages include:

  • jacsId - Unique document identifier
  • jacsVersion - Version tracking
  • jacsSignature - Cryptographic signature
  • jacsHash - Content hash for integrity

Passthrough Mode

If JACS cannot verify an incoming message, it falls back to plain JSON parsing. This allows:

  • Gradual migration to JACS-secured communication
  • Interoperability with non-JACS MCP clients/servers

To require JACS verification (no fallback), implement custom validation in your tools.

Key Management

Each MCP server and client needs its own JACS agent with:

  • Unique agent ID
  • Private/public key pair
  • Configuration file

Debugging

Enable Debug Logging

export JACS_MCP_DEBUG=true

This outputs detailed logs about message signing and verification.

STDIO Debug Note

For STDIO transports, debug logs go to stderr to prevent contaminating the JSON-RPC stream on stdout.

Common Issues

"JACS not operational": Check that your config file path is correct and the agent is properly initialized.

Verification failures: Ensure both server and client are using compatible JACS versions and valid keys.

Empty responses: The proxy removes null values from messages to prevent MCP schema validation issues.

Next Steps

HTTP Server

JACS provides middleware and utilities for building HTTP servers with cryptographic request/response signing. This enables secure communication between JACS agents over HTTP.

Overview

JACS HTTP integration provides:

  • Request signing: Sign outgoing HTTP requests with your agent's key
  • Request verification: Verify incoming requests were signed by a valid agent
  • Response signing: Automatically sign responses before sending
  • Response verification: Verify server responses on the client side
  • Framework middleware: Ready-to-use middleware for Express and Koa

Core Concepts

Request/Response Flow

Client                          Server
  |                               |
  |-- signRequest(payload) -----> |
  |                               |-- verifyResponse() --> payload
  |                               |-- process payload
  |                               |-- signResponse(result)
  |<-- verifyResponse(result) ---|
  |

All messages are cryptographically signed, ensuring:

  • Message integrity (no tampering)
  • Agent identity (verified sender)
  • Non-repudiation (proof of origin)

HTTP Client

Basic Client Usage

import jacs from 'jacsnpm';
import http from 'http';

async function main() {
  // Load JACS agent
  await jacs.load('./jacs.config.json');

  // Prepare payload
  const payload = {
    message: "Hello, secure server!",
    data: { id: 123, value: "some data" },
    timestamp: new Date().toISOString()
  };

  // Sign the request
  const signedRequest = await jacs.signRequest(payload);

  // Send HTTP request
  const response = await sendRequest(signedRequest, 'localhost', 3000, '/api/echo');

  // Verify the response
  const verifiedResponse = await jacs.verifyResponse(response);
  console.log('Verified payload:', verifiedResponse.payload);
}

function sendRequest(body, host, port, path) {
  return new Promise((resolve, reject) => {
    const options = {
      hostname: host,
      port: port,
      path: path,
      method: 'POST',
      headers: {
        'Content-Type': 'text/plain',
        'Content-Length': Buffer.byteLength(body),
      },
    };

    const req = http.request(options, (res) => {
      let data = '';
      res.on('data', chunk => data += chunk);
      res.on('end', () => {
        if (res.statusCode >= 200 && res.statusCode < 300) {
          resolve(data);
        } else {
          reject(new Error(`HTTP ${res.statusCode}: ${data}`));
        }
      });
    });

    req.on('error', reject);
    req.write(body);
    req.end();
  });
}

main();

Using Fetch

import jacs from 'jacsnpm';

async function sendJacsRequest(url, payload) {
  await jacs.load('./jacs.config.json');

  // Sign the payload
  const signedRequest = await jacs.signRequest(payload);

  // Send request
  const response = await fetch(url, {
    method: 'POST',
    headers: { 'Content-Type': 'text/plain' },
    body: signedRequest
  });

  if (!response.ok) {
    throw new Error(`HTTP ${response.status}`);
  }

  // Verify response
  const responseText = await response.text();
  const verified = await jacs.verifyResponse(responseText);

  return verified.payload;
}

// Usage
const result = await sendJacsRequest('http://localhost:3000/api/data', {
  action: 'fetch',
  query: { id: 42 }
});

Express Server

Using Express Middleware

JACS provides JACSExpressMiddleware that automatically:

  • Verifies incoming JACS requests
  • Attaches the verified payload to req.jacsPayload
  • Signs outgoing responses when you call res.send() with an object
import express from 'express';
import { JACSExpressMiddleware } from 'jacsnpm/http';

const app = express();
const PORT = 3000;

// IMPORTANT: Use express.text() BEFORE JACS middleware
// This ensures req.body is a string for JACS verification
app.use('/api', express.text({ type: '*/*' }));

// Apply JACS middleware to API routes
app.use('/api', JACSExpressMiddleware({
  configPath: './jacs.server.config.json'
}));

// Route handler
app.post('/api/echo', (req, res) => {
  // Access verified payload from req.jacsPayload
  const payload = req.jacsPayload;

  if (!payload) {
    return res.status(400).send('JACS payload missing');
  }

  console.log('Received verified payload:', payload);

  // Send response object - middleware will sign it automatically
  res.send({
    echo: "Server says hello!",
    received: payload,
    timestamp: new Date().toISOString()
  });
});

app.listen(PORT, () => {
  console.log(`JACS Express server listening on port ${PORT}`);
});

Middleware Configuration

JACSExpressMiddleware({
  configPath: './jacs.config.json'  // Required: path to JACS config
})

Manual Request/Response Handling

For more control, you can handle signing manually:

import express from 'express';
import jacs from 'jacsnpm';

const app = express();

// Initialize JACS once at startup
await jacs.load('./jacs.config.json');

app.use(express.text({ type: '*/*' }));

app.post('/api/process', async (req, res) => {
  try {
    // Manually verify incoming request
    const verified = await jacs.verifyResponse(req.body);
    const payload = verified.payload;

    // Process the request
    const result = {
      success: true,
      data: processData(payload),
      timestamp: new Date().toISOString()
    };

    // Manually sign the response
    const signedResponse = await jacs.signResponse(result);
    res.type('text/plain').send(signedResponse);

  } catch (error) {
    console.error('JACS verification failed:', error);
    res.status(400).send('Invalid JACS request');
  }
});

Koa Server

Using Koa Middleware

import Koa from 'koa';
import { JACSKoaMiddleware } from 'jacsnpm/http';

const app = new Koa();
const PORT = 3000;

// Apply JACS Koa middleware
// Handles raw body reading, verification, and response signing
app.use(JACSKoaMiddleware({
  configPath: './jacs.server.config.json'
}));

// Route handler
app.use(async (ctx) => {
  if (ctx.path === '/api/echo' && ctx.method === 'POST') {
    // Access verified payload from ctx.state.jacsPayload or ctx.jacsPayload
    const payload = ctx.state.jacsPayload || ctx.jacsPayload;

    if (!payload) {
      ctx.status = 400;
      ctx.body = 'JACS payload missing';
      return;
    }

    console.log('Received verified payload:', payload);

    // Set response object - middleware will sign it automatically
    ctx.body = {
      echo: "Koa server says hello!",
      received: payload,
      timestamp: new Date().toISOString()
    };
  } else {
    ctx.status = 404;
    ctx.body = 'Not Found. Try POST to /api/echo';
  }
});

app.listen(PORT, () => {
  console.log(`JACS Koa server listening on port ${PORT}`);
});

API Reference

jacs.signRequest(payload)

Sign an object as a JACS request.

const signedRequest = await jacs.signRequest({
  method: 'getData',
  params: { id: 123 }
});
// Returns: JACS-signed JSON string

jacs.verifyResponse(responseString)

Verify a JACS-signed response and extract the payload.

const result = await jacs.verifyResponse(jacsResponseString);
// Returns: { payload: {...}, jacsId: '...', ... }

const payload = result.payload;

jacs.signResponse(payload)

Sign an object as a JACS response.

const signedResponse = await jacs.signResponse({
  success: true,
  data: { result: 42 }
});
// Returns: JACS-signed JSON string

JACSExpressMiddleware(options)

Express middleware for JACS request/response handling.

import { JACSExpressMiddleware } from 'jacsnpm/http';

app.use(JACSExpressMiddleware({
  configPath: './jacs.config.json'  // Required
}));

Request Processing:

  • Reads req.body as a JACS string
  • Verifies the signature
  • Attaches payload to req.jacsPayload
  • On failure, sends 400 response

Response Processing:

  • Intercepts res.send() calls
  • If body is an object, signs it as JACS
  • Sends signed string to client

JACSKoaMiddleware(options)

Koa middleware for JACS request/response handling.

import { JACSKoaMiddleware } from 'jacsnpm/http';

app.use(JACSKoaMiddleware({
  configPath: './jacs.config.json'  // Required
}));

Request Processing:

  • Reads raw request body
  • Verifies JACS signature
  • Attaches payload to ctx.state.jacsPayload and ctx.jacsPayload

Response Processing:

  • Signs ctx.body if it's an object
  • Converts to JACS string before sending

Complete Example

Server (server.js)

import express from 'express';
import { JACSExpressMiddleware } from 'jacsnpm/http';

const app = express();

// JACS middleware for /api routes
app.use('/api', express.text({ type: '*/*' }));
app.use('/api', JACSExpressMiddleware({
  configPath: './jacs.server.config.json'
}));

// Echo endpoint
app.post('/api/echo', (req, res) => {
  const payload = req.jacsPayload;
  res.send({
    echo: payload,
    serverTime: new Date().toISOString()
  });
});

// Calculate endpoint
app.post('/api/calculate', (req, res) => {
  const { operation, a, b } = req.jacsPayload;

  let result;
  switch (operation) {
    case 'add': result = a + b; break;
    case 'subtract': result = a - b; break;
    case 'multiply': result = a * b; break;
    case 'divide': result = a / b; break;
    default: return res.status(400).send({ error: 'Unknown operation' });
  }

  res.send({ operation, a, b, result });
});

app.listen(3000, () => console.log('Server running on port 3000'));

Client (client.js)

import jacs from 'jacsnpm';

async function main() {
  await jacs.load('./jacs.client.config.json');

  // Call echo endpoint
  const echoResult = await callApi('/api/echo', {
    message: 'Hello, server!'
  });
  console.log('Echo result:', echoResult);

  // Call calculate endpoint
  const calcResult = await callApi('/api/calculate', {
    operation: 'multiply',
    a: 7,
    b: 6
  });
  console.log('Calculate result:', calcResult);
}

async function callApi(path, payload) {
  const signedRequest = await jacs.signRequest(payload);

  const response = await fetch(`http://localhost:3000${path}`, {
    method: 'POST',
    headers: { 'Content-Type': 'text/plain' },
    body: signedRequest
  });

  const responseText = await response.text();
  const verified = await jacs.verifyResponse(responseText);
  return verified.payload;
}

main().catch(console.error);

Security Considerations

Content-Type

JACS requests should use text/plain content type since they are signed JSON strings, not raw JSON.

Error Handling

Always handle verification failures gracefully:

try {
  const verified = await jacs.verifyResponse(responseText);
  return verified.payload;
} catch (error) {
  console.error('JACS verification failed:', error.message);
  // Handle invalid/tampered response
}

Agent Keys

Each server and client needs its own JACS agent with:

  • Unique agent ID
  • Private/public key pair
  • Configuration file pointing to the keys

Middleware Order

For Express, ensure express.text() comes before JACSExpressMiddleware:

// Correct order
app.use('/api', express.text({ type: '*/*' }));
app.use('/api', JACSExpressMiddleware({ configPath: '...' }));

// Wrong - JACS middleware won't receive string body
app.use('/api', JACSExpressMiddleware({ configPath: '...' }));
app.use('/api', express.text({ type: '*/*' }));

Next Steps

Express Middleware

This chapter covers advanced Express.js integration patterns with JACS, building on the basics covered in HTTP Server.

Overview

JACS provides JACSExpressMiddleware for seamless integration with Express.js applications:

  • Automatic request verification
  • Automatic response signing
  • Access to verified payloads via req.jacsPayload
  • Error handling for invalid requests

Quick Start

import express from 'express';
import { JACSExpressMiddleware } from 'jacsnpm/http';

const app = express();

// Required: Parse body as text before JACS middleware
app.use('/api', express.text({ type: '*/*' }));

// Apply JACS middleware
app.use('/api', JACSExpressMiddleware({
  configPath: './jacs.config.json'
}));

// Routes automatically get verified payloads and signed responses
app.post('/api/data', (req, res) => {
  const payload = req.jacsPayload;
  res.send({ received: payload, status: 'ok' });
});

app.listen(3000);

Middleware Configuration

Basic Configuration

JACSExpressMiddleware({
  configPath: './jacs.config.json'  // Required: path to JACS config
})

Per-Route Configuration

Apply JACS to specific routes:

const app = express();

// Non-JACS routes (public endpoints)
app.get('/health', (req, res) => res.send({ status: 'ok' }));
app.get('/public/info', (req, res) => res.send({ name: 'My API' }));

// JACS-protected routes
app.use('/api', express.text({ type: '*/*' }));
app.use('/api', JACSExpressMiddleware({ configPath: './jacs.config.json' }));

app.post('/api/secure', (req, res) => {
  // Only JACS-signed requests reach here
  res.send({ data: 'secure response' });
});

Multiple JACS Agents

Use different JACS agents for different routes:

// Admin routes with admin agent
app.use('/admin', express.text({ type: '*/*' }));
app.use('/admin', JACSExpressMiddleware({
  configPath: './jacs.admin.config.json'
}));

// User routes with user agent
app.use('/user', express.text({ type: '*/*' }));
app.use('/user', JACSExpressMiddleware({
  configPath: './jacs.user.config.json'
}));

Request Handling

Accessing Verified Payload

The middleware attaches the verified payload to req.jacsPayload:

app.post('/api/process', (req, res) => {
  // req.jacsPayload contains the verified, decrypted payload
  const { action, data, timestamp } = req.jacsPayload;

  console.log('Action:', action);
  console.log('Data:', data);
  console.log('Request timestamp:', timestamp);

  res.send({ processed: true });
});

Handling Missing Payload

If JACS verification fails, req.jacsPayload will be undefined:

app.post('/api/secure', (req, res) => {
  if (!req.jacsPayload) {
    return res.status(400).json({ error: 'Invalid JACS request' });
  }

  // Process verified payload
  res.send({ success: true });
});

Validation Helper

Create a reusable validation middleware:

function requireJacsPayload(req, res, next) {
  if (!req.jacsPayload) {
    return res.status(400).json({
      error: 'JACS verification failed',
      message: 'Request must be signed with valid JACS credentials'
    });
  }
  next();
}

// Apply to routes
app.post('/api/secure', requireJacsPayload, (req, res) => {
  // Guaranteed to have valid req.jacsPayload
  res.send({ data: req.jacsPayload });
});

Response Handling

Automatic Signing

When you call res.send() with an object, the middleware automatically signs it:

app.post('/api/data', (req, res) => {
  // This object will be automatically JACS-signed
  res.send({
    result: 'success',
    data: { value: 42 },
    timestamp: new Date().toISOString()
  });
});

Sending Unsigned Responses

To bypass automatic signing, send a string directly:

app.post('/api/raw', (req, res) => {
  // String responses are not signed
  res.type('text/plain').send('Raw text response');
});

Custom Response Format

app.post('/api/custom', (req, res) => {
  const response = {
    success: true,
    payload: {
      action: 'completed',
      result: processRequest(req.jacsPayload)
    },
    metadata: {
      serverTime: new Date().toISOString(),
      requestId: generateRequestId()
    }
  };

  // Automatically signed before sending
  res.send(response);
});

Error Handling

Global Error Handler

import express from 'express';
import { JACSExpressMiddleware } from 'jacsnpm/http';

const app = express();

app.use('/api', express.text({ type: '*/*' }));
app.use('/api', JACSExpressMiddleware({ configPath: './jacs.config.json' }));

app.post('/api/process', (req, res, next) => {
  try {
    if (!req.jacsPayload) {
      throw new Error('Missing JACS payload');
    }

    const result = processData(req.jacsPayload);
    res.send({ result });
  } catch (error) {
    next(error);
  }
});

// Global error handler
app.use((error, req, res, next) => {
  console.error('Error:', error.message);

  res.status(500).send({
    error: 'Internal server error',
    message: error.message
  });
});

Typed Errors

class JacsValidationError extends Error {
  constructor(message) {
    super(message);
    this.name = 'JacsValidationError';
    this.statusCode = 400;
  }
}

app.post('/api/validate', (req, res, next) => {
  try {
    if (!req.jacsPayload) {
      throw new JacsValidationError('Invalid JACS request');
    }

    const { requiredField } = req.jacsPayload;
    if (!requiredField) {
      throw new JacsValidationError('Missing required field');
    }

    res.send({ valid: true });
  } catch (error) {
    next(error);
  }
});

// Error handler
app.use((error, req, res, next) => {
  const statusCode = error.statusCode || 500;
  res.status(statusCode).send({
    error: error.name,
    message: error.message
  });
});

Advanced Patterns

Router-Level Middleware

import { Router } from 'express';
import { JACSExpressMiddleware } from 'jacsnpm/http';

// Create a JACS-enabled router
function createJacsRouter(configPath) {
  const router = Router();

  router.use(express.text({ type: '*/*' }));
  router.use(JACSExpressMiddleware({ configPath }));

  return router;
}

// Usage
const apiRouter = createJacsRouter('./jacs.config.json');

apiRouter.post('/users', (req, res) => {
  res.send({ users: getUserList() });
});

apiRouter.post('/orders', (req, res) => {
  res.send({ orders: getOrders(req.jacsPayload.userId) });
});

app.use('/api', apiRouter);

Middleware Composition

Combine JACS with other middleware:

import rateLimit from 'express-rate-limit';
import { JACSExpressMiddleware } from 'jacsnpm/http';

const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100 // limit each IP to 100 requests per windowMs
});

// Apply multiple middleware in order
app.use('/api',
  limiter,                              // Rate limiting first
  express.text({ type: '*/*' }),        // Parse body as text
  JACSExpressMiddleware({ configPath: './jacs.config.json' })  // JACS verification
);

Logging Middleware

Log JACS requests for auditing:

function jacsLogger(req, res, next) {
  if (req.jacsPayload) {
    console.log(JSON.stringify({
      timestamp: new Date().toISOString(),
      method: req.method,
      path: req.path,
      jacsPayload: req.jacsPayload,
      ip: req.ip
    }));
  }
  next();
}

app.use('/api', express.text({ type: '*/*' }));
app.use('/api', JACSExpressMiddleware({ configPath: './jacs.config.json' }));
app.use('/api', jacsLogger);  // After JACS middleware

Authentication Integration

Combine JACS with user authentication:

// JACS middleware first
app.use('/api', express.text({ type: '*/*' }));
app.use('/api', JACSExpressMiddleware({ configPath: './jacs.config.json' }));

// Then authentication check
function requireAuth(req, res, next) {
  const payload = req.jacsPayload;

  if (!payload || !payload.userId) {
    return res.status(401).send({ error: 'Authentication required' });
  }

  // Attach user to request
  req.user = { id: payload.userId };
  next();
}

app.post('/api/protected', requireAuth, (req, res) => {
  res.send({
    message: `Hello, user ${req.user.id}`,
    data: req.jacsPayload.data
  });
});

Testing

Unit Testing Routes

import request from 'supertest';
import jacs from 'jacsnpm';

describe('JACS API', () => {
  beforeAll(async () => {
    await jacs.load('./jacs.test.config.json');
  });

  it('should accept valid JACS requests', async () => {
    const payload = { action: 'test', data: 'hello' };
    const signedRequest = await jacs.signRequest(payload);

    const response = await request(app)
      .post('/api/echo')
      .set('Content-Type', 'text/plain')
      .send(signedRequest);

    expect(response.status).toBe(200);

    // Verify response is JACS-signed
    const verified = await jacs.verifyResponse(response.text);
    expect(verified.payload.echo).toEqual(payload);
  });

  it('should reject unsigned requests', async () => {
    const response = await request(app)
      .post('/api/echo')
      .set('Content-Type', 'text/plain')
      .send('{"invalid": "request"}');

    expect(response.status).toBe(400);
  });
});

Mock JACS for Testing

// test/mocks/jacs.js
export const mockJacs = {
  payload: null,

  setPayload(p) {
    this.payload = p;
  },

  reset() {
    this.payload = null;
  }
};

// Mock middleware for testing
export function mockJacsMiddleware(req, res, next) {
  req.jacsPayload = mockJacs.payload;
  next();
}

// In tests
describe('API without real JACS', () => {
  beforeEach(() => {
    mockJacs.setPayload({ userId: 'test-user', action: 'test' });
  });

  afterEach(() => {
    mockJacs.reset();
  });

  it('processes payload correctly', async () => {
    const response = await request(testApp)
      .post('/api/process')
      .send('test');

    expect(response.status).toBe(200);
  });
});

Complete Application Example

import express from 'express';
import { JACSExpressMiddleware } from 'jacsnpm/http';

const app = express();

// Health check (no JACS)
app.get('/health', (req, res) => res.send({ status: 'healthy' }));

// JACS-protected API routes
app.use('/api', express.text({ type: '*/*' }));
app.use('/api', JACSExpressMiddleware({
  configPath: './jacs.config.json'
}));

// Validation middleware
function requirePayload(req, res, next) {
  if (!req.jacsPayload) {
    return res.status(400).send({ error: 'Invalid JACS request' });
  }
  next();
}

// Routes
app.post('/api/echo', requirePayload, (req, res) => {
  res.send({ echo: req.jacsPayload });
});

app.post('/api/users', requirePayload, (req, res) => {
  const { name, email } = req.jacsPayload;

  if (!name || !email) {
    return res.status(400).send({ error: 'Name and email required' });
  }

  const user = createUser({ name, email });
  res.send({ user, created: true });
});

app.post('/api/documents', requirePayload, async (req, res) => {
  const { title, content } = req.jacsPayload;

  const document = await createDocument({ title, content });
  res.send({ document });
});

// Error handler
app.use((err, req, res, next) => {
  console.error('Error:', err);
  res.status(500).send({ error: 'Internal server error' });
});

// Start server
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`JACS Express server listening on port ${PORT}`);
});

Troubleshooting

Body Parsing Issues

Problem: req.jacsPayload is always undefined

Solution: Ensure express.text() comes before JACS middleware:

// Correct
app.use('/api', express.text({ type: '*/*' }));
app.use('/api', JACSExpressMiddleware({ configPath: '...' }));

// Wrong
app.use('/api', JACSExpressMiddleware({ configPath: '...' }));
app.use('/api', express.text({ type: '*/*' }));

JSON Body Parser Conflict

Problem: Using express.json() interferes with JACS

Solution: Use route-specific middleware:

// JSON for non-JACS routes
app.use('/public', express.json());

// Text for JACS routes
app.use('/api', express.text({ type: '*/*' }));
app.use('/api', JACSExpressMiddleware({ configPath: '...' }));

Response Not Signed

Problem: Responses are plain JSON, not JACS-signed

Solution: Ensure you're sending an object, not a string:

// Will be signed
res.send({ data: 'value' });

// Will NOT be signed
res.send(JSON.stringify({ data: 'value' }));

Next Steps

API Reference

Complete API documentation for the jacsnpm Node.js package.

Installation

npm install jacsnpm

Core Module

import { JacsAgent, hashString, createConfig } from 'jacsnpm';

JacsAgent Class

The JacsAgent class is the primary interface for JACS operations. Each instance maintains its own state and can be used independently, allowing multiple agents in the same process.

Constructor

new JacsAgent()

Creates a new empty JacsAgent instance. Call load() to initialize with a configuration.

Example:

const agent = new JacsAgent();
agent.load('./jacs.config.json');

agent.load(configPath)

Load and initialize the agent from a configuration file.

Parameters:

  • configPath (string): Path to the JACS configuration file

Returns: string - The loaded agent's JSON

Example:

const agent = new JacsAgent();
const agentJson = agent.load('./jacs.config.json');
console.log('Agent loaded:', JSON.parse(agentJson).jacsId);

agent.createDocument(documentString, customSchema?, outputFilename?, noSave?, attachments?, embed?)

Create and sign a new JACS document.

Parameters:

  • documentString (string): JSON string of the document content
  • customSchema (string, optional): Path to a custom JSON Schema for validation
  • outputFilename (string, optional): Filename to save the document
  • noSave (boolean, optional): If true, don't save to storage (default: false)
  • attachments (string, optional): Path to file attachments
  • embed (boolean, optional): If true, embed attachments in the document

Returns: string - The signed document as a JSON string

Example:

// Basic document creation
const doc = agent.createDocument(JSON.stringify({
  title: 'My Document',
  content: 'Hello, World!'
}));

// With custom schema
const validatedDoc = agent.createDocument(
  JSON.stringify({ title: 'Validated', amount: 100 }),
  './schemas/invoice.schema.json'
);

// Without saving
const tempDoc = agent.createDocument(
  JSON.stringify({ data: 'temporary' }),
  null,
  null,
  true  // noSave = true
);

// With attachments
const docWithFile = agent.createDocument(
  JSON.stringify({ report: 'Monthly Report' }),
  null,
  null,
  false,
  './report.pdf',
  true  // embed = true
);

agent.verifyDocument(documentString)

Verify a document's signature and hash integrity.

Parameters:

  • documentString (string): The signed document JSON string

Returns: boolean - True if the document is valid

Example:

const isValid = agent.verifyDocument(signedDocumentJson);
if (isValid) {
  console.log('Document signature verified');
} else {
  console.log('Document verification failed');
}

agent.verifySignature(documentString, signatureField?)

Verify a document's signature with an optional custom signature field.

Parameters:

  • documentString (string): The signed document JSON string
  • signatureField (string, optional): Name of the signature field (default: 'jacsSignature')

Returns: boolean - True if the signature is valid

Example:

// Verify default signature field
const isValid = agent.verifySignature(docJson);

// Verify custom signature field
const isValidCustom = agent.verifySignature(docJson, 'customSignature');

agent.updateDocument(documentKey, newDocumentString, attachments?, embed?)

Update an existing document, creating a new version.

Parameters:

  • documentKey (string): The document key in format "id:version"
  • newDocumentString (string): The modified document as JSON string
  • attachments (Array, optional): Array of attachment file paths
  • embed (boolean, optional): If true, embed attachments

Returns: string - The updated document as a JSON string

Example:

// Parse existing document to get key
const doc = JSON.parse(signedDoc);
const documentKey = `${doc.jacsId}:${doc.jacsVersion}`;

// Update the document
const updatedDoc = agent.updateDocument(
  documentKey,
  JSON.stringify({
    ...doc,
    title: 'Updated Title',
    content: 'Modified content'
  })
);

agent.createAgreement(documentString, agentIds, question?, context?, agreementFieldName?)

Add an agreement requiring multiple agent signatures to a document.

Parameters:

  • documentString (string): The document JSON string
  • agentIds (Array): Array of agent IDs required to sign
  • question (string, optional): The agreement question
  • context (string, optional): Additional context for the agreement
  • agreementFieldName (string, optional): Field name for the agreement (default: 'jacsAgreement')

Returns: string - The document with agreement as a JSON string

Example:

const docWithAgreement = agent.createAgreement(
  signedDocumentJson,
  ['agent-1-uuid', 'agent-2-uuid', 'agent-3-uuid'],
  'Do you agree to these terms?',
  'Q1 2024 Service Agreement',
  'jacsAgreement'
);

agent.signAgreement(documentString, agreementFieldName?)

Sign an agreement as the current agent.

Parameters:

  • documentString (string): The document with agreement JSON string
  • agreementFieldName (string, optional): Field name of the agreement (default: 'jacsAgreement')

Returns: string - The document with this agent's signature added

Example:

const signedAgreement = agent.signAgreement(
  docWithAgreementJson,
  'jacsAgreement'
);

agent.checkAgreement(documentString, agreementFieldName?)

Check the status of an agreement (which agents have signed).

Parameters:

  • documentString (string): The document with agreement JSON string
  • agreementFieldName (string, optional): Field name of the agreement (default: 'jacsAgreement')

Returns: string - JSON string with agreement status

Example:

const statusJson = agent.checkAgreement(signedAgreementJson);
const status = JSON.parse(statusJson);

console.log('Required signers:', status.required);
console.log('Signatures received:', status.signed);
console.log('Complete:', status.complete);

agent.signString(data)

Sign arbitrary string data with the agent's private key.

Parameters:

  • data (string): The data to sign

Returns: string - Base64-encoded signature

Example:

const signature = agent.signString('Important message');
console.log('Signature:', signature);

agent.verifyString(data, signatureBase64, publicKey, publicKeyEncType)

Verify a signature on arbitrary string data.

Parameters:

  • data (string): The original data
  • signatureBase64 (string): The base64-encoded signature
  • publicKey (Buffer): The public key as a Buffer
  • publicKeyEncType (string): The key algorithm (e.g., 'ring-Ed25519')

Returns: boolean - True if the signature is valid

Example:

const isValid = agent.verifyString(
  'Important message',
  signatureBase64,
  publicKeyBuffer,
  'ring-Ed25519'
);

agent.signRequest(params)

Sign a request payload, wrapping it in a JACS document.

Parameters:

  • params (any): The request payload object

Returns: string - JACS-signed request as a JSON string

Example:

const signedRequest = agent.signRequest({
  method: 'GET',
  path: '/api/data',
  timestamp: new Date().toISOString(),
  body: { query: 'value' }
});

agent.verifyResponse(documentString)

Verify a JACS-signed response and extract the payload.

Parameters:

  • documentString (string): The JACS-signed response

Returns: object - Object containing the verified payload

Example:

const result = agent.verifyResponse(jacsResponseString);
const payload = result.payload;
console.log('Verified payload:', payload);

agent.verifyResponseWithAgentId(documentString)

Verify a response and return both the payload and signer's agent ID.

Parameters:

  • documentString (string): The JACS-signed response

Returns: object - Object with payload and agent ID

Example:

const result = agent.verifyResponseWithAgentId(jacsResponseString);
console.log('Payload:', result.payload);
console.log('Signed by agent:', result.agentId);

agent.verifyAgent(agentFile?)

Verify the agent's own signature and hash, or verify another agent file.

Parameters:

  • agentFile (string, optional): Path to an agent file to verify

Returns: boolean - True if the agent is valid

Example:

// Verify the loaded agent
const isValid = agent.verifyAgent();

// Verify another agent file
const isOtherValid = agent.verifyAgent('./other-agent.json');

agent.updateAgent(newAgentString)

Update the agent document with new data.

Parameters:

  • newAgentString (string): The modified agent document as JSON string

Returns: string - The updated agent document

Example:

const currentAgent = JSON.parse(agent.load('./jacs.config.json'));
const updatedAgent = agent.updateAgent(JSON.stringify({
  ...currentAgent,
  description: 'Updated description'
}));

agent.signAgent(agentString, publicKey, publicKeyEncType)

Sign another agent's document with a registration signature.

Parameters:

  • agentString (string): The agent document to sign
  • publicKey (Buffer): The public key as a Buffer
  • publicKeyEncType (string): The key algorithm

Returns: string - The signed agent document

Example:

const signedAgent = agent.signAgent(
  externalAgentJson,
  publicKeyBuffer,
  'ring-Ed25519'
);

Utility Functions

hashString(data)

Hash a string using SHA-256.

Parameters:

  • data (string): The string to hash

Returns: string - Hexadecimal hash string

Example:

import { hashString } from 'jacsnpm';

const hash = hashString('data to hash');
console.log('SHA-256:', hash);

createConfig(options)

Create a JACS configuration JSON string programmatically.

Parameters:

  • jacsUseSecurity (string, optional): Enable security features
  • jacsDataDirectory (string, optional): Directory for data storage
  • jacsKeyDirectory (string, optional): Directory for key storage
  • jacsAgentPrivateKeyFilename (string, optional): Private key filename
  • jacsAgentPublicKeyFilename (string, optional): Public key filename
  • jacsAgentKeyAlgorithm (string, optional): Signing algorithm
  • jacsPrivateKeyPassword (string, optional): Password for private key
  • jacsAgentIdAndVersion (string, optional): Agent ID and version to load
  • jacsDefaultStorage (string, optional): Storage backend ('fs', 's3', 'memory')

Returns: string - Configuration as JSON string

Example:

import { createConfig } from 'jacsnpm';

const configJson = createConfig(
  undefined,           // jacsUseSecurity
  './jacs_data',       // jacsDataDirectory
  './jacs_keys',       // jacsKeyDirectory
  undefined,           // private key filename
  undefined,           // public key filename
  'ring-Ed25519',      // algorithm
  undefined,           // password
  undefined,           // agent id
  'fs'                 // storage
);

// Write to file
fs.writeFileSync('jacs.config.json', configJson);

HTTP Module

import { JACSExpressMiddleware, JACSKoaMiddleware } from 'jacsnpm/http';

JACSExpressMiddleware(options)

Express middleware for JACS request/response handling.

Parameters:

  • options.configPath (string): Path to JACS configuration file

Returns: Express middleware function

Example:

import { JACSExpressMiddleware } from 'jacsnpm/http';

app.use('/api', express.text({ type: '*/*' }));
app.use('/api', JACSExpressMiddleware({
  configPath: './jacs.config.json'
}));

app.post('/api/data', (req, res) => {
  // req.jacsPayload contains verified payload
  res.send({ received: req.jacsPayload });
});

JACSKoaMiddleware(options)

Koa middleware for JACS request/response handling.

Parameters:

  • options.configPath (string): Path to JACS configuration file

Returns: Koa middleware function

Example:

import { JACSKoaMiddleware } from 'jacsnpm/http';

app.use(JACSKoaMiddleware({
  configPath: './jacs.config.json'
}));

app.use(async (ctx) => {
  // ctx.state.jacsPayload contains verified payload
  ctx.body = { received: ctx.state.jacsPayload };
});

MCP Module

import {
  JACSTransportProxy,
  createJACSTransportProxy,
  createJACSTransportProxyAsync
} from 'jacsnpm/mcp';

JACSTransportProxy

Class that wraps MCP transports with JACS encryption.

Constructor:

new JACSTransportProxy(transport, role, jacsConfigPath)

Parameters:

  • transport: Any MCP transport (Stdio, SSE, WebSocket)
  • role (string): 'server' or 'client'
  • jacsConfigPath (string): Path to JACS configuration file

createJACSTransportProxy(transport, configPath, role)

Factory function for creating a transport proxy.

Parameters:

  • transport: The underlying MCP transport
  • configPath (string): Path to JACS configuration file
  • role (string): 'server' or 'client'

Returns: JACSTransportProxy instance

Example:

import { createJACSTransportProxy } from 'jacsnpm/mcp';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';

const baseTransport = new StdioServerTransport();
const secureTransport = createJACSTransportProxy(
  baseTransport,
  './jacs.config.json',
  'server'
);

createJACSTransportProxyAsync(transport, configPath, role)

Async factory that waits for JACS to be fully loaded.

Parameters: Same as createJACSTransportProxy

Returns: Promise

Example:

const secureTransport = await createJACSTransportProxyAsync(
  baseTransport,
  './jacs.config.json',
  'server'
);

TypeScript Support

The package includes full TypeScript definitions. Import types as needed:

import { JacsAgent, hashString, createConfig } from 'jacsnpm';

const agent: JacsAgent = new JacsAgent();
const hash: string = hashString('data');
const config: string = createConfig(
  undefined,
  './data',
  './keys'
);

Deprecated Functions

The following module-level functions are deprecated. Use new JacsAgent() and instance methods instead:

  • load() - Use agent.load()
  • signAgent() - Use agent.signAgent()
  • verifyString() - Use agent.verifyString()
  • signString() - Use agent.signString()
  • verifyAgent() - Use agent.verifyAgent()
  • updateAgent() - Use agent.updateAgent()
  • verifyDocument() - Use agent.verifyDocument()
  • updateDocument() - Use agent.updateDocument()
  • verifySignature() - Use agent.verifySignature()
  • createAgreement() - Use agent.createAgreement()
  • signAgreement() - Use agent.signAgreement()
  • createDocument() - Use agent.createDocument()
  • checkAgreement() - Use agent.checkAgreement()
  • signRequest() - Use agent.signRequest()
  • verifyResponse() - Use agent.verifyResponse()
  • verifyResponseWithAgentId() - Use agent.verifyResponseWithAgentId()

Migration Example:

// Old (deprecated)
import jacs from 'jacsnpm';
await jacs.load('./jacs.config.json');
const doc = jacs.createDocument(JSON.stringify({ data: 'test' }));

// New (recommended)
import { JacsAgent } from 'jacsnpm';
const agent = new JacsAgent();
agent.load('./jacs.config.json');
const doc = agent.createDocument(JSON.stringify({ data: 'test' }));

Error Handling

All methods may throw errors. Use try/catch for error handling:

try {
  const agent = new JacsAgent();
  agent.load('./jacs.config.json');
  const doc = agent.createDocument(JSON.stringify({ data: 'test' }));
} catch (error) {
  console.error('JACS error:', error.message);
}

See Also

Python Installation

The JACS Python package (jacs) provides Python bindings to the JACS Rust library, making it easy to integrate JACS into AI/ML workflows, data science projects, and Python applications.

Requirements

  • Python: Version 3.10 or higher
  • pip: For package management
  • Operating System: Linux, macOS, or Windows with WSL
  • Architecture: x86_64 or ARM64

Installation

Using pip

pip install jacs

Using conda

conda install -c conda-forge jacs

Using poetry

poetry add jacs

Development Installation

# Clone the repository
git clone https://github.com/HumanAssisted/JACS
cd JACS/jacspy

# Create virtual environment
python -m venv .venv
source .venv/bin/activate  # On Windows: .venv\Scripts\activate

# Install in development mode
pip install -e .

Verify Installation

Create a simple test to verify everything is working:

# test.py
import jacs

print('JACS Python bindings loaded successfully!')

# Test basic functionality
try:
    agent = jacs.JacsAgent()
    agent.load('./jacs.config.json')
    print('Agent loaded successfully!')
except Exception as error:
    print(f'Error loading agent: {error}')

Run the test:

python test.py

Package Structure

The jacs package provides Python bindings to the JACS Rust library:

Core Module

import jacs

# Create and load agent
agent = jacs.JacsAgent()
agent.load('./jacs.config.json')

# Utility functions
hash_value = jacs.hash_string("data to hash")
config_json = jacs.create_config(
    jacs_data_directory="./data",
    jacs_key_directory="./keys",
    jacs_agent_key_algorithm="ring-Ed25519"
)

JacsAgent Methods

# Create a new agent instance
agent = jacs.JacsAgent()

# Load configuration
agent.load('./jacs.config.json')

# Document operations
signed_doc = agent.create_document(json_string)
is_valid = agent.verify_document(document_string)

# Signing operations
signature = agent.sign_string("data to sign")

Configuration

Configuration File

Create a jacs.config.json file:

{
  "$schema": "https://hai.ai/schemas/jacs.config.schema.json",
  "jacs_data_directory": "./jacs_data",
  "jacs_key_directory": "./jacs_keys",
  "jacs_default_storage": "fs",
  "jacs_agent_key_algorithm": "ring-Ed25519",
  "jacs_agent_id_and_version": "agent-uuid:version-uuid"
}

Load Configuration in Python

import jacs

# Create agent and load configuration
agent = jacs.JacsAgent()
agent.load('./jacs.config.json')

Programmatic Configuration

import jacs

# Create a configuration JSON string programmatically
config_json = jacs.create_config(
    jacs_data_directory="./jacs_data",
    jacs_key_directory="./jacs_keys",
    jacs_agent_key_algorithm="ring-Ed25519",
    jacs_default_storage="fs"
)

Environment Variables

JACS reads environment variables that override configuration file settings:

export JACS_DATA_DIRECTORY="./production_data"
export JACS_KEY_DIRECTORY="./production_keys"
export JACS_AGENT_KEY_ALGORITHM="ring-Ed25519"
export JACS_DEFAULT_STORAGE="fs"

Storage Backends

Configure storage in jacs.config.json:

File System (Default)

{
  "jacs_default_storage": "fs",
  "jacs_data_directory": "./jacs_data",
  "jacs_key_directory": "./jacs_keys"
}

S3 Storage

{
  "jacs_default_storage": "s3"
}

S3 credentials are read from standard AWS environment variables.

Memory Storage (Testing)

{
  "jacs_default_storage": "memory"
}

Cryptographic Algorithms

{
  "jacs_agent_key_algorithm": "ring-Ed25519"
}

Pros: Fast, secure, small signatures Cons: Requires elliptic curve support

RSA-PSS

{
  "jacs_agent_key_algorithm": "RSA-PSS"
}

Pros: Widely supported, proven security Cons: Larger signatures, slower

pq-dilithium (Post-Quantum)

{
  "jacs_agent_key_algorithm": "pq-dilithium"
}

Pros: Quantum-resistant Cons: Experimental, large signatures

pq2025 (Post-Quantum Hybrid)

{
  "jacs_agent_key_algorithm": "pq2025"
}

Pros: Combines ML-DSA-87 with hybrid approach Cons: Newest algorithm, largest signatures

Development Setup

Project Structure

my-jacs-project/
├── requirements.txt
├── jacs.config.json
├── src/
│   ├── agent.py
│   ├── tasks.py
│   └── agreements.py
├── jacs_data/
│   ├── agents/
│   ├── tasks/
│   └── documents/
├── jacs_keys/
│   ├── private.pem
│   └── public.pem
└── tests/
    └── test_jacs.py

Requirements.txt Setup

jacs>=0.1.0
fastapi>=0.100.0  # For FastMCP integration
uvicorn>=0.23.0   # For ASGI server
pydantic>=2.0.0   # For data validation

Basic Application

# src/app.py
import jacs
import json

def main():
    # Create and load agent
    agent = jacs.JacsAgent()
    agent.load('./jacs.config.json')

    # Create a document
    document_data = {
        "title": "My First Document",
        "content": "Hello from Python!"
    }

    signed_doc = agent.create_document(json.dumps(document_data))
    print('Document created')

    # Verify the document
    is_valid = agent.verify_document(signed_doc)
    print(f'Document valid: {is_valid}')

    print('JACS agent ready!')
    return agent

if __name__ == "__main__":
    agent = main()

Virtual Environment Setup

Using venv

# Create virtual environment
python -m venv jacs-env

# Activate (Linux/macOS)
source jacs-env/bin/activate

# Activate (Windows)
jacs-env\Scripts\activate

# Install JACS
pip install jacs

Using conda

# Create conda environment
conda create -n jacs-env python=3.11

# Activate environment
conda activate jacs-env

# Install JACS
pip install jacs

Using poetry

# Initialize poetry project
poetry init

# Add JACS dependency
poetry add jacs

# Install dependencies
poetry install

# Activate shell
poetry shell

Jupyter Notebook Setup

# Install Jupyter in your JACS environment
pip install jupyter

# Start Jupyter
jupyter notebook
# In your notebook
import jacs
import json

# Create and load agent
agent = jacs.JacsAgent()
agent.load('./jacs.config.json')

# Create a simple document
doc = agent.create_document(json.dumps({
    "title": "Notebook Analysis",
    "data": [1, 2, 3, 4, 5]
}))

print("Document created!")
print("JACS ready for notebook use!")

Common Issues

Module Not Found

If you get ModuleNotFoundError: No module named 'jacs':

# Check Python version
python --version  # Should be 3.10+

# Check if jacs is installed
pip list | grep jacs

# Reinstall if needed
pip uninstall jacs
pip install jacs

Permission Errors

If you get permission errors accessing files:

# Check directory permissions
ls -la jacs_data/ jacs_keys/

# Fix permissions
chmod 755 jacs_data/ jacs_keys/
chmod 600 jacs_keys/*.pem

Binary Compatibility

If you get binary compatibility errors:

# Update pip and reinstall
pip install --upgrade pip
pip uninstall jacs
pip install jacs --no-cache-dir

Windows Issues

On Windows, you may need Visual C++ Build Tools:

# Install Visual C++ Build Tools
# Or use conda-forge
conda install -c conda-forge jacs

Type Hints and IDE Support

JACS is built with Rust and PyO3, providing Python bindings:

import jacs
import json

# Create agent instance
agent: jacs.JacsAgent = jacs.JacsAgent()
agent.load('./jacs.config.json')

# Create and verify documents
signed_doc: str = agent.create_document(json.dumps({"title": "Test"}))
is_valid: bool = agent.verify_document(signed_doc)

Testing Setup

# tests/test_jacs.py
import unittest
import jacs
import json

class TestJACS(unittest.TestCase):
    def setUp(self):
        # Requires a valid jacs.config.json file
        self.agent = jacs.JacsAgent()
        self.agent.load('./jacs.config.json')

    def test_document_creation(self):
        doc_data = {"title": "Test Document", "content": "Test content"}
        signed_doc = self.agent.create_document(json.dumps(doc_data))

        # Document should be a valid JSON string
        parsed = json.loads(signed_doc)
        self.assertIn("jacsId", parsed)
        self.assertIn("jacsSignature", parsed)

    def test_document_verification(self):
        doc_data = {"title": "Verify Test"}
        signed_doc = self.agent.create_document(json.dumps(doc_data))

        is_valid = self.agent.verify_document(signed_doc)
        self.assertTrue(is_valid)

    def test_sign_string(self):
        signature = self.agent.sign_string("test data")
        self.assertIsInstance(signature, str)
        self.assertTrue(len(signature) > 0)

if __name__ == "__main__":
    unittest.main()

Next Steps

Now that you have JACS installed:

  1. Basic Usage - Learn core JACS operations
  2. MCP Integration - Add Model Context Protocol support
  3. FastMCP Integration - Build advanced MCP servers
  4. API Reference - Complete API documentation

Examples

Check out the complete examples in the examples directory:

  • Basic agent creation and task management
  • Jupyter notebook workflows
  • FastMCP server implementation
  • AI/ML pipeline integration

Basic Usage

This chapter covers fundamental JACS operations in Python, including agent initialization, document creation, signing, and verification.

Initializing an Agent

Create and Load Agent

import jacs

# Create a new agent instance
agent = jacs.JacsAgent()

# Load configuration from file
agent.load('./jacs.config.json')

Configuration File

Create jacs.config.json:

{
  "$schema": "https://hai.ai/schemas/jacs.config.schema.json",
  "jacs_data_directory": "./jacs_data",
  "jacs_key_directory": "./jacs_keys",
  "jacs_default_storage": "fs",
  "jacs_agent_key_algorithm": "ring-Ed25519",
  "jacs_agent_id_and_version": "agent-uuid:version-uuid"
}

Creating Documents

Basic Document Creation

import jacs
import json

agent = jacs.JacsAgent()
agent.load('./jacs.config.json')

# Create a document from JSON
document_data = {
    "title": "Project Proposal",
    "content": "Quarterly development plan",
    "budget": 50000
}

signed_document = agent.create_document(json.dumps(document_data))
print('Signed document:', signed_document)

With Custom Schema

Validate against a custom JSON Schema:

signed_document = agent.create_document(
    json.dumps(document_data),
    './schemas/proposal.schema.json'  # custom schema path
)

With Output File

signed_document = agent.create_document(
    json.dumps(document_data),
    None,                     # no custom schema
    './output/proposal.json'  # output filename
)

Without Saving

signed_document = agent.create_document(
    json.dumps(document_data),
    None,   # no custom schema
    None,   # no output filename
    True    # no_save = True
)

With Attachments

signed_document = agent.create_document(
    json.dumps(document_data),
    None,                        # no custom schema
    None,                        # no output filename
    False,                       # save the document
    './attachments/report.pdf',  # attachment path
    True                         # embed files
)

Verifying Documents

Verify Document Signature

# Verify a document's signature and hash
is_valid = agent.verify_document(signed_document_json)
print('Document valid:', is_valid)

Verify Specific Signature Field

# Verify with a custom signature field
is_valid = agent.verify_signature(
    signed_document_json,
    'jacsSignature'  # signature field name
)

Updating Documents

Update Existing Document

# Original document key format: "id:version"
document_key = 'doc-uuid:version-uuid'

# Modified document content (must include jacsId and jacsVersion)
updated_data = {
    "jacsId": "doc-uuid",
    "jacsVersion": "version-uuid",
    "title": "Updated Proposal",
    "content": "Revised quarterly plan",
    "budget": 75000
}

updated_document = agent.update_document(
    document_key,
    json.dumps(updated_data)
)

print('Updated document:', updated_document)

Update with New Attachments

updated_document = agent.update_document(
    document_key,
    json.dumps(updated_data),
    ['./new-report.pdf'],  # new attachments
    True                   # embed files
)

Signing and Verification

Sign Arbitrary Data

# Sign any string data
signature = agent.sign_string('Important message to sign')
print('Signature:', signature)

Verify Arbitrary Data

# Verify a signature on string data
is_valid = agent.verify_string(
    'Important message to sign',  # original data
    signature_base64,             # base64 signature
    public_key_bytes,             # public key as bytes
    'ring-Ed25519'                # algorithm
)

Working with Agreements

Create an Agreement

# Add agreement requiring multiple agent signatures
document_with_agreement = agent.create_agreement(
    signed_document_json,
    ['agent1-uuid', 'agent2-uuid'],           # required signers
    'Do you agree to these terms?',            # question
    'Q1 2024 service contract',                # context
    'jacsAgreement'                            # field name
)

Sign an Agreement

# Sign the agreement as the current agent
signed_agreement = agent.sign_agreement(
    document_with_agreement_json,
    'jacsAgreement'  # agreement field name
)

Check Agreement Status

# Check which agents have signed
status = agent.check_agreement(
    document_with_agreement_json,
    'jacsAgreement'
)

print('Agreement status:', json.loads(status))

Agent Operations

Verify Agent

# Verify the loaded agent's signature
is_valid = agent.verify_agent()
print('Agent valid:', is_valid)

# Verify a specific agent file
is_valid_other = agent.verify_agent('./other-agent.json')

Update Agent

# Update agent document
updated_agent_json = agent.update_agent(json.dumps({
    "jacsId": "agent-uuid",
    "jacsVersion": "version-uuid",
    "name": "Updated Agent Name",
    "description": "Updated description"
}))

Sign External Agent

# Sign another agent's document with registration signature
signed_agent_json = agent.sign_agent(
    external_agent_json,
    public_key_bytes,
    'ring-Ed25519'
)

Request/Response Signing

Sign a Request

# Sign request parameters as a JACS document
signed_request = agent.sign_request({
    "method": "GET",
    "path": "/api/resource",
    "timestamp": datetime.now().isoformat(),
    "body": {"query": "data"}
})

Verify a Response

# Verify a signed response
result = agent.verify_response(signed_response_json)
print('Response valid:', result)

# Verify and get signer's agent ID
result_with_id = agent.verify_response_with_agent_id(signed_response_json)
print('Signer ID:', result_with_id)

Utility Functions

Hash String

import jacs

# SHA-256 hash of a string
hash_value = jacs.hash_string('data to hash')
print('Hash:', hash_value)

Create Configuration

import jacs

# Programmatically create a config JSON string
config_json = jacs.create_config(
    jacs_data_directory='./jacs_data',
    jacs_key_directory='./jacs_keys',
    jacs_agent_key_algorithm='ring-Ed25519',
    jacs_default_storage='fs'
)

print('Config:', config_json)

Error Handling

import jacs

agent = jacs.JacsAgent()

try:
    agent.load('./jacs.config.json')
except Exception as error:
    print(f'Failed to load agent: {error}')

try:
    doc = agent.create_document(json.dumps({'data': 'test'}))
    print('Document created')
except Exception as error:
    print(f'Failed to create document: {error}')

try:
    is_valid = agent.verify_document(invalid_json)
except Exception as error:
    print(f'Verification failed: {error}')

Complete Example

import jacs
import json

def main():
    # Initialize agent
    agent = jacs.JacsAgent()
    agent.load('./jacs.config.json')

    # Create a task document
    task = {
        "title": "Code Review",
        "description": "Review pull request #123",
        "assignee": "developer-uuid",
        "deadline": "2024-02-01"
    }

    signed_task = agent.create_document(json.dumps(task))
    print('Task created')

    # Verify the task
    if agent.verify_document(signed_task):
        print('Task signature valid')

    # Create agreement for task acceptance
    task_with_agreement = agent.create_agreement(
        signed_task,
        ['manager-uuid', 'developer-uuid'],
        'Do you accept this task assignment?'
    )

    # Sign the agreement
    signed_agreement = agent.sign_agreement(task_with_agreement)
    print('Agreement signed')

    # Check agreement status
    status = agent.check_agreement(signed_agreement)
    print('Status:', status)

    # Hash some data for reference
    task_hash = jacs.hash_string(signed_task)
    print('Task hash:', task_hash)

if __name__ == "__main__":
    main()

Working with Document Data

Parse Signed Documents

import json

# Create and sign a document
doc_data = {"title": "My Document", "content": "Hello, World!"}
signed_doc = agent.create_document(json.dumps(doc_data))

# Parse the signed document to access JACS fields
parsed = json.loads(signed_doc)
print('Document ID:', parsed.get('jacsId'))
print('Document Version:', parsed.get('jacsVersion'))
print('Signature:', parsed.get('jacsSignature'))

Document Key Format

# Document keys combine ID and version
doc_id = parsed['jacsId']
doc_version = parsed['jacsVersion']
document_key = f"{doc_id}:{doc_version}"

# Use the key for updates
updated_doc = agent.update_document(document_key, json.dumps({
    **parsed,
    "content": "Updated content"
}))

Configuration Management

Load from File

import jacs

agent = jacs.JacsAgent()
agent.load('./jacs.config.json')

Environment Variables

JACS reads environment variables that override configuration file settings:

export JACS_DATA_DIRECTORY="./production_data"
export JACS_KEY_DIRECTORY="./production_keys"
export JACS_AGENT_KEY_ALGORITHM="ring-Ed25519"
export JACS_DEFAULT_STORAGE="fs"

Programmatic Configuration

import jacs
import json
import os

# Create config programmatically
config_json = jacs.create_config(
    jacs_data_directory='./jacs_data',
    jacs_key_directory='./jacs_keys',
    jacs_agent_key_algorithm='ring-Ed25519',
    jacs_default_storage='fs'
)

# Write to file
with open('jacs.config.json', 'w') as f:
    f.write(config_json)

# Then load it
agent = jacs.JacsAgent()
agent.load('./jacs.config.json')

Next Steps

MCP Integration

JACS provides seamless integration with the Model Context Protocol (MCP), enabling cryptographically signed and verified communication between AI agents and MCP servers. This integration ensures that all tool calls, resource requests, and prompt interactions are authenticated and tamper-proof.

Overview

JACS MCP integration provides:

  • Cryptographic Authentication: All MCP messages are signed and verified
  • FastMCP Support: Native integration with FastMCP servers
  • HTTP & SSE Transports: Support for Server-Sent Events transport
  • Transparent Security: Existing MCP code works with minimal changes

Quick Start

Basic MCP Server with JACS

import jacs
import os
from pathlib import Path
from jacs.mcp import JACSMCPServer
from fastmcp import FastMCP
import uvicorn

# Setup JACS configuration
current_dir = Path(__file__).parent.absolute()
jacs_config_path = current_dir / "jacs.config.json"

# Initialize JACS agent
agent = jacs.JacsAgent()
agent.load(str(jacs_config_path))

# Create FastMCP server with JACS authentication
mcp = JACSMCPServer(FastMCP("Authenticated Echo Server"))

@mcp.tool()
def echo_tool(text: str) -> str:
    """Echo the input text with server prefix"""
    return f"SERVER SAYS: {text}"

@mcp.resource("echo://static")
def echo_resource() -> str:
    return "Echo!"

@mcp.prompt("echo")
def echo_prompt(text: str) -> str:
    return f"Echo prompt: {text}"

# Get the ASGI app with JACS middleware
sse_app_with_middleware = mcp.sse_app()

if __name__ == "__main__":
    print("Starting JACS-enabled MCP server...")
    uvicorn.run(sse_app_with_middleware, host="localhost", port=8000)

Basic MCP Client with JACS

import asyncio
import os
from pathlib import Path
import jacs
from jacs.mcp import JACSMCPClient

# Setup JACS configuration
current_dir = Path(__file__).parent.absolute()
jacs_config_path = current_dir / "jacs.client.config.json"

# Initialize JACS agent
agent = jacs.JacsAgent()
agent.load(str(jacs_config_path))

async def main():
    server_url = "http://localhost:8000/sse"

    try:
        client = JACSMCPClient(server_url)

        async with client:
            # Call authenticated tool
            result = await client.call_tool("echo_tool", {
                "text": "Hello from authenticated client!"
            })
            print(f"Tool result: {result}")

            # Read authenticated resource
            resource = await client.read_resource("echo://static")
            print(f"Resource: {resource}")

    except Exception as e:
        print(f"Error: {e}")

if __name__ == "__main__":
    asyncio.run(main())

How It Works

JACSMCPServer

The JACSMCPServer wrapper adds JACS middleware to a FastMCP server:

  1. Incoming Requests: Intercepts JSON-RPC requests and verifies them using jacs.verify_request()
  2. Outgoing Responses: Signs JSON-RPC responses using jacs.sign_response()
from jacs.mcp import JACSMCPServer
from fastmcp import FastMCP

# Create FastMCP server
base_server = FastMCP("My Server")

# Wrap with JACS authentication
authenticated_server = JACSMCPServer(base_server)

# All decorators work normally
@authenticated_server.tool()
def my_tool(data: str) -> str:
    return f"Processed: {data}"

# Get ASGI app with JACS middleware
app = authenticated_server.sse_app()

JACSMCPClient

The JACSMCPClient wrapper adds interceptors to a FastMCP client:

  1. Outgoing Messages: Signs messages using jacs.sign_request()
  2. Incoming Messages: Verifies messages using jacs.verify_response()
from jacs.mcp import JACSMCPClient

client = JACSMCPClient("http://localhost:8000/sse")

async with client:
    result = await client.call_tool("my_tool", {"data": "test"})

Configuration

JACS Configuration File

Create a jacs.config.json file for your server and client:

{
  "$schema": "https://hai.ai/schemas/jacs.config.schema.json",
  "jacs_agent_id_and_version": "your-agent-id:version",
  "jacs_agent_key_algorithm": "ring-Ed25519",
  "jacs_agent_private_key_filename": "private.pem",
  "jacs_agent_public_key_filename": "public.pem",
  "jacs_data_directory": "./jacs_data",
  "jacs_default_storage": "fs",
  "jacs_key_directory": "./jacs_keys"
}

Initializing the Agent

Before using MCP integration, initialize your JACS agent:

import jacs

# Create and load agent
agent = jacs.JacsAgent()
agent.load("./jacs.config.json")

# Agent is now ready for MCP operations

Integration Patterns

FastMCP with JACS Middleware

from jacs.mcp import JACSMCPServer
from fastmcp import FastMCP
import jacs

# Initialize JACS
agent = jacs.JacsAgent()
agent.load("./jacs.config.json")

# Create and wrap server
server = FastMCP("My Server")
authenticated_server = JACSMCPServer(server)

@authenticated_server.tool()
def secure_tool(input_data: str) -> str:
    """A tool that processes signed input"""
    return f"Securely processed: {input_data}"

# Run server
if __name__ == "__main__":
    import uvicorn
    app = authenticated_server.sse_app()
    uvicorn.run(app, host="localhost", port=8000)

Manual Request/Response Signing

For custom integrations, you can use the module-level functions directly:

import jacs

# Initialize agent first
agent = jacs.JacsAgent()
agent.load("./jacs.config.json")

# Sign a request
signed_request = jacs.sign_request({
    "method": "tools/call",
    "params": {"name": "my_tool", "arguments": {"data": "test"}}
})

# Verify a response
verified_response = jacs.verify_response(signed_response_string)
payload = verified_response.get("payload")

Error Handling

Common Errors

import jacs
from jacs.mcp import JACSMCPClient

async def robust_mcp_client():
    try:
        agent = jacs.JacsAgent()
        agent.load("./jacs.config.json")

        client = JACSMCPClient("http://localhost:8000/sse")
        async with client:
            result = await client.call_tool("my_tool", {"data": "test"})
            return result

    except FileNotFoundError as e:
        print(f"Configuration file not found: {e}")

    except ConnectionError as e:
        print(f"MCP connection failed: {e}")

    except Exception as e:
        print(f"Unexpected error: {e}")

Debugging

Enable logging to debug authentication issues:

import logging

# Enable detailed logging
logging.basicConfig(level=logging.DEBUG)

# Your MCP code here...

Production Deployment

Security Best Practices

  1. Key Management: Store private keys securely
  2. Environment Variables: Use environment variables for sensitive paths
  3. Network Security: Use TLS for network transport
  4. Key Rotation: Implement key rotation policies
import os
import jacs

# Production initialization
config_path = os.getenv("JACS_CONFIG_PATH", "/etc/jacs/config.json")

agent = jacs.JacsAgent()
agent.load(config_path)

Docker Deployment

FROM python:3.11-slim

WORKDIR /app

# Install dependencies
COPY requirements.txt .
RUN pip install -r requirements.txt

# Copy application
COPY . .

# Create secure key directory
RUN mkdir -p /secure/keys && chmod 700 /secure/keys

# Set environment variables
ENV JACS_CONFIG_PATH=/app/jacs.config.json

# Run MCP server
CMD ["python", "mcp_server.py"]

Testing

Unit Testing MCP Tools

import pytest
import jacs
from jacs.mcp import JACSMCPServer
from fastmcp import FastMCP
from fastmcp.client import Client
from fastmcp.client.transports import FastMCPTransport

@pytest.fixture
def jacs_agent():
    agent = jacs.JacsAgent()
    agent.load("./test.config.json")
    return agent

@pytest.fixture
def jacs_mcp_server(jacs_agent):
    server = FastMCP("Test Server")
    return JACSMCPServer(server)

async def test_authenticated_tool(jacs_mcp_server):
    @jacs_mcp_server.tool()
    def echo(text: str) -> str:
        return f"Echo: {text}"

    # Test the tool directly
    result = echo("test")
    assert "test" in result

API Reference

JACSMCPServer(mcp_server)

Wraps a FastMCP server with JACS authentication middleware.

Parameters:

  • mcp_server: A FastMCP server instance

Returns: The wrapped server with JACS middleware

Example:

from jacs.mcp import JACSMCPServer
from fastmcp import FastMCP

server = FastMCP("My Server")
authenticated = JACSMCPServer(server)
app = authenticated.sse_app()

JACSMCPClient(url, **kwargs)

Creates a FastMCP client with JACS authentication interceptors.

Parameters:

  • url: The MCP server SSE endpoint URL
  • **kwargs: Additional arguments passed to the FastMCP Client

Returns: A FastMCP Client with JACS interceptors

Example:

from jacs.mcp import JACSMCPClient

client = JACSMCPClient("http://localhost:8000/sse")
async with client:
    result = await client.call_tool("my_tool", {"arg": "value"})

Module Functions

These functions are used internally by the MCP integration:

  • jacs.sign_request(data) - Sign a request payload
  • jacs.verify_request(data) - Verify an incoming request
  • jacs.sign_response(data) - Sign a response payload
  • jacs.verify_response(data) - Verify an incoming response

Next Steps

API Reference

Complete API documentation for the jacs Python package.

Installation

pip install jacs

Core Module

import jacs
from jacs import JacsAgent

JacsAgent Class

The JacsAgent class is the primary interface for JACS operations. Each instance maintains its own state and can be used independently, allowing multiple agents in the same process.

Constructor

JacsAgent()

Creates a new empty JacsAgent instance. Call load() to initialize with a configuration.

Example:

agent = jacs.JacsAgent()
agent.load('./jacs.config.json')

agent.load(config_path)

Load and initialize the agent from a configuration file.

Parameters:

  • config_path (str): Path to the JACS configuration file

Returns: str - The loaded agent's JSON

Example:

agent = jacs.JacsAgent()
agent_json = agent.load('./jacs.config.json')
print('Agent loaded:', json.loads(agent_json)['jacsId'])

agent.create_document(document_string, custom_schema=None, output_filename=None, no_save=False, attachments=None, embed=False)

Create and sign a new JACS document.

Parameters:

  • document_string (str): JSON string of the document content
  • custom_schema (str, optional): Path to a custom JSON Schema for validation
  • output_filename (str, optional): Filename to save the document
  • no_save (bool, optional): If True, don't save to storage (default: False)
  • attachments (str, optional): Path to file attachments
  • embed (bool, optional): If True, embed attachments in the document

Returns: str - The signed document as a JSON string

Example:

# Basic document creation
doc = agent.create_document(json.dumps({
    'title': 'My Document',
    'content': 'Hello, World!'
}))

# With custom schema
validated_doc = agent.create_document(
    json.dumps({'title': 'Validated', 'amount': 100}),
    custom_schema='./schemas/invoice.schema.json'
)

# Without saving
temp_doc = agent.create_document(
    json.dumps({'data': 'temporary'}),
    no_save=True
)

# With attachments
doc_with_file = agent.create_document(
    json.dumps({'report': 'Monthly Report'}),
    attachments='./report.pdf',
    embed=True
)

agent.verify_document(document_string)

Verify a document's signature and hash integrity.

Parameters:

  • document_string (str): The signed document JSON string

Returns: bool - True if the document is valid

Example:

is_valid = agent.verify_document(signed_document_json)
if is_valid:
    print('Document signature verified')
else:
    print('Document verification failed')

agent.verify_signature(document_string, signature_field=None)

Verify a document's signature with an optional custom signature field.

Parameters:

  • document_string (str): The signed document JSON string
  • signature_field (str, optional): Name of the signature field (default: 'jacsSignature')

Returns: bool - True if the signature is valid

Example:

# Verify default signature field
is_valid = agent.verify_signature(doc_json)

# Verify custom signature field
is_valid_custom = agent.verify_signature(doc_json, 'customSignature')

agent.update_document(document_key, new_document_string, attachments=None, embed=False)

Update an existing document, creating a new version.

Parameters:

  • document_key (str): The document key in format "id:version"
  • new_document_string (str): The modified document as JSON string
  • attachments (list, optional): List of attachment file paths
  • embed (bool, optional): If True, embed attachments

Returns: str - The updated document as a JSON string

Example:

# Parse existing document to get key
doc = json.loads(signed_doc)
document_key = f"{doc['jacsId']}:{doc['jacsVersion']}"

# Update the document
updated_doc = agent.update_document(
    document_key,
    json.dumps({
        **doc,
        'title': 'Updated Title',
        'content': 'Modified content'
    })
)

agent.create_agreement(document_string, agent_ids, question=None, context=None, agreement_field_name=None)

Add an agreement requiring multiple agent signatures to a document.

Parameters:

  • document_string (str): The document JSON string
  • agent_ids (list): List of agent IDs required to sign
  • question (str, optional): The agreement question
  • context (str, optional): Additional context for the agreement
  • agreement_field_name (str, optional): Field name for the agreement (default: 'jacsAgreement')

Returns: str - The document with agreement as a JSON string

Example:

doc_with_agreement = agent.create_agreement(
    signed_document_json,
    ['agent-1-uuid', 'agent-2-uuid', 'agent-3-uuid'],
    question='Do you agree to these terms?',
    context='Q1 2024 Service Agreement',
    agreement_field_name='jacsAgreement'
)

agent.sign_agreement(document_string, agreement_field_name=None)

Sign an agreement as the current agent.

Parameters:

  • document_string (str): The document with agreement JSON string
  • agreement_field_name (str, optional): Field name of the agreement (default: 'jacsAgreement')

Returns: str - The document with this agent's signature added

Example:

signed_agreement = agent.sign_agreement(
    doc_with_agreement_json,
    'jacsAgreement'
)

agent.check_agreement(document_string, agreement_field_name=None)

Check the status of an agreement (which agents have signed).

Parameters:

  • document_string (str): The document with agreement JSON string
  • agreement_field_name (str, optional): Field name of the agreement (default: 'jacsAgreement')

Returns: str - JSON string with agreement status

Example:

status_json = agent.check_agreement(signed_agreement_json)
status = json.loads(status_json)

print('Required signers:', status['required'])
print('Signatures received:', status['signed'])
print('Complete:', status['complete'])

agent.sign_string(data)

Sign arbitrary string data with the agent's private key.

Parameters:

  • data (str): The data to sign

Returns: str - Base64-encoded signature

Example:

signature = agent.sign_string('Important message')
print('Signature:', signature)

agent.verify_string(data, signature_base64, public_key, public_key_enc_type)

Verify a signature on arbitrary string data.

Parameters:

  • data (str): The original data
  • signature_base64 (str): The base64-encoded signature
  • public_key (bytes): The public key as bytes
  • public_key_enc_type (str): The key algorithm (e.g., 'ring-Ed25519')

Returns: bool - True if the signature is valid

Example:

is_valid = agent.verify_string(
    'Important message',
    signature_base64,
    public_key_bytes,
    'ring-Ed25519'
)

agent.sign_request(params)

Sign a request payload, wrapping it in a JACS document.

Parameters:

  • params (any): The request payload (will be JSON serialized)

Returns: str - JACS-signed request as a JSON string

Example:

signed_request = agent.sign_request({
    'method': 'GET',
    'path': '/api/data',
    'timestamp': datetime.now().isoformat(),
    'body': {'query': 'value'}
})

agent.verify_response(document_string)

Verify a JACS-signed response and extract the payload.

Parameters:

  • document_string (str): The JACS-signed response

Returns: dict - Dictionary containing the verified payload

Example:

result = agent.verify_response(jacs_response_string)
payload = result.get('payload')
print('Verified payload:', payload)

agent.verify_response_with_agent_id(document_string)

Verify a response and return both the payload and signer's agent ID.

Parameters:

  • document_string (str): The JACS-signed response

Returns: dict - Dictionary with payload and agent ID

Example:

result = agent.verify_response_with_agent_id(jacs_response_string)
print('Payload:', result['payload'])
print('Signed by agent:', result['agentId'])

agent.verify_agent(agent_file=None)

Verify the agent's own signature and hash, or verify another agent file.

Parameters:

  • agent_file (str, optional): Path to an agent file to verify

Returns: bool - True if the agent is valid

Example:

# Verify the loaded agent
is_valid = agent.verify_agent()

# Verify another agent file
is_other_valid = agent.verify_agent('./other-agent.json')

agent.update_agent(new_agent_string)

Update the agent document with new data.

Parameters:

  • new_agent_string (str): The modified agent document as JSON string

Returns: str - The updated agent document

Example:

current_agent = json.loads(agent.load('./jacs.config.json'))
updated_agent = agent.update_agent(json.dumps({
    **current_agent,
    'description': 'Updated description'
}))

agent.sign_agent(agent_string, public_key, public_key_enc_type)

Sign another agent's document with a registration signature.

Parameters:

  • agent_string (str): The agent document to sign
  • public_key (bytes): The public key as bytes
  • public_key_enc_type (str): The key algorithm

Returns: str - The signed agent document

Example:

signed_agent = agent.sign_agent(
    external_agent_json,
    public_key_bytes,
    'ring-Ed25519'
)

Module-Level Functions

These functions operate on a global agent singleton and are maintained for backwards compatibility. New code should use the JacsAgent class instead.

jacs.load(config_path)

Load the global agent from a configuration file.

import jacs
jacs.load('./jacs.config.json')

jacs.sign_request(data)

Sign a request using the global agent.

signed = jacs.sign_request({'method': 'tools/call', 'params': {...}})

jacs.verify_request(data)

Verify an incoming request using the global agent.

payload = jacs.verify_request(incoming_request_string)

jacs.sign_response(data)

Sign a response using the global agent.

signed = jacs.sign_response({'result': 'success'})

jacs.verify_response(data)

Verify an incoming response using the global agent.

result = jacs.verify_response(response_string)
payload = result.get('payload')

MCP Module

from jacs.mcp import JACSMCPServer, JACSMCPClient

JACSMCPServer(mcp_server)

Wraps a FastMCP server with JACS authentication middleware.

Parameters:

  • mcp_server: A FastMCP server instance

Returns: The wrapped server with JACS middleware

Example:

from jacs.mcp import JACSMCPServer
from fastmcp import FastMCP

server = FastMCP("My Server")
authenticated = JACSMCPServer(server)
app = authenticated.sse_app()

JACSMCPClient(url, **kwargs)

Creates a FastMCP client with JACS authentication interceptors.

Parameters:

  • url: The MCP server SSE endpoint URL
  • **kwargs: Additional arguments passed to the FastMCP Client

Returns: A FastMCP Client with JACS interceptors

Example:

from jacs.mcp import JACSMCPClient

client = JACSMCPClient("http://localhost:8000/sse")
async with client:
    result = await client.call_tool("my_tool", {"arg": "value"})

Configuration

Configuration File Format

Create a jacs.config.json file:

{
  "$schema": "https://hai.ai/schemas/jacs.config.schema.json",
  "jacs_agent_id_and_version": "your-agent-id:version",
  "jacs_agent_key_algorithm": "ring-Ed25519",
  "jacs_agent_private_key_filename": "private.pem",
  "jacs_agent_public_key_filename": "public.pem",
  "jacs_data_directory": "./jacs_data",
  "jacs_default_storage": "fs",
  "jacs_key_directory": "./jacs_keys"
}

Configuration Options

FieldTypeDescription
jacs_agent_id_and_versionstringAgent ID and version in format "id:version"
jacs_agent_key_algorithmstringSigning algorithm: "ring-Ed25519", "RSA-PSS", "pq-dilithium", "pq2025"
jacs_agent_private_key_filenamestringPrivate key filename
jacs_agent_public_key_filenamestringPublic key filename
jacs_data_directorystringDirectory for data storage
jacs_key_directorystringDirectory for key storage
jacs_default_storagestringStorage backend: "fs", "s3", "memory"

Error Handling

All methods may raise exceptions. Use try/except for error handling:

try:
    agent = jacs.JacsAgent()
    agent.load('./jacs.config.json')
    doc = agent.create_document(json.dumps({'data': 'test'}))
except FileNotFoundError as e:
    print(f'Configuration file not found: {e}')
except ValueError as e:
    print(f'Invalid configuration: {e}')
except Exception as e:
    print(f'JACS error: {e}')

Common Exceptions

ExceptionDescription
FileNotFoundErrorConfiguration file or key file not found
ValueErrorInvalid configuration or document format
RuntimeErrorAgent not loaded or cryptographic operation failed

Type Hints

The package supports type hints for better IDE integration:

from jacs import JacsAgent
import json

def process_document(agent: JacsAgent, data: dict) -> str:
    """Create and return a signed document."""
    doc_string = json.dumps(data)
    return agent.create_document(doc_string)

def verify_and_extract(agent: JacsAgent, doc: str) -> dict:
    """Verify document and extract content."""
    if agent.verify_document(doc):
        return json.loads(doc)
    raise ValueError("Document verification failed")

Thread Safety

JacsAgent instances use internal locking and are thread-safe. You can safely use the same agent instance across multiple threads:

import threading
from jacs import JacsAgent

agent = JacsAgent()
agent.load('./jacs.config.json')

def worker(data):
    # Safe to call from multiple threads
    doc = agent.create_document(json.dumps(data))
    return doc

threads = [
    threading.Thread(target=worker, args=({'id': i},))
    for i in range(10)
]
for t in threads:
    t.start()
for t in threads:
    t.join()

See Also

JSON Schemas

JACS uses JSON Schema (Draft-07) to define the structure and validation rules for all documents in the system. These schemas ensure consistency, enable validation, and provide a contract for interoperability between agents.

Schema Architecture

JACS schemas follow a hierarchical composition pattern:

┌─────────────────────────────────────────────────────────┐
│                    Document Schemas                      │
│  (agent.schema.json, task.schema.json, message.schema.json)  │
└─────────────────────────────────────────────────────────┘
                           │
                           ▼
┌─────────────────────────────────────────────────────────┐
│                   Header Schema                          │
│              (header.schema.json)                        │
│  Base fields: jacsId, jacsVersion, jacsSignature, etc.  │
└─────────────────────────────────────────────────────────┘
                           │
                           ▼
┌─────────────────────────────────────────────────────────┐
│                 Component Schemas                        │
│   signature.schema.json, agreement.schema.json,         │
│   files.schema.json, embedding.schema.json, etc.        │
└─────────────────────────────────────────────────────────┘

Schema Categories

Configuration Schema

SchemaPurpose
jacs.config.schema.jsonAgent configuration file format

Document Schemas

SchemaPurpose
header/v1/header.schema.jsonBase fields for all JACS documents
agent/v1/agent.schema.jsonAgent identity and capabilities
task/v1/task.schema.jsonTask workflow and state management
message/v1/message.schema.jsonInter-agent messages
node/v1/node.schema.jsonGraph node representation
program/v1/program.schema.jsonExecutable program definitions
eval/v1/eval.schema.jsonEvaluation and assessment records

Component Schemas

SchemaPurpose
signature/v1/signature.schema.jsonCryptographic signatures
agreement/v1/agreement.schema.jsonMulti-party agreements
files/v1/files.schema.jsonFile attachments
embedding/v1/embedding.schema.jsonVector embeddings
contact/v1/contact.schema.jsonContact information
service/v1/service.schema.jsonService definitions
tool/v1/tool.schema.jsonTool capabilities
action/v1/action.schema.jsonAction definitions
unit/v1/unit.schema.jsonUnit of measurement

Schema Locations

Schemas are available at:

  • HTTPS URLs: https://hai.ai/schemas/...
  • Local files: jacs/schemas/...

Example schema URLs:

https://hai.ai/schemas/jacs.config.schema.json
https://hai.ai/schemas/header/v1/header.schema.json
https://hai.ai/schemas/agent/v1/agent.schema.json
https://hai.ai/schemas/components/signature/v1/signature.schema.json

Using Schemas

In Documents

Every JACS document must include a $schema field:

{
  "$schema": "https://hai.ai/schemas/agent/v1/agent.schema.json",
  "jacsId": "550e8400-e29b-41d4-a716-446655440000",
  "jacsVersion": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
  "jacsType": "agent",
  ...
}

In Configuration Files

Reference the config schema for IDE support:

{
  "$schema": "https://hai.ai/schemas/jacs.config.schema.json",
  "jacs_data_directory": "./jacs_data",
  "jacs_key_directory": "./jacs_keys",
  ...
}

Custom Schema Validation

Validate documents against custom schemas:

import jacs
import json

agent = jacs.JacsAgent()
agent.load('./jacs.config.json')

# Create document with custom schema
doc = agent.create_document(
    json.dumps({'invoice_id': 'INV-001', 'amount': 100.00}),
    custom_schema='./schemas/invoice.schema.json'
)
import { JacsAgent } from 'jacsnpm';

const agent = new JacsAgent();
agent.load('./jacs.config.json');

// Create document with custom schema
const doc = agent.createDocument(
  JSON.stringify({ invoice_id: 'INV-001', amount: 100.00 }),
  './schemas/invoice.schema.json'
);

HAI Extensions

JACS schemas include a custom hai property that categorizes fields:

ValueDescription
metaMetadata fields (IDs, dates, versions)
baseCore cryptographic fields (hashes, signatures)
agentAgent-controlled content fields

This categorization helps determine which fields should be included in hash calculations and signature operations.

Versioning

Schemas are versioned using directory paths:

schemas/
├── header/
│   └── v1/
│       └── header.schema.json
├── agent/
│   └── v1/
│       └── agent.schema.json
└── components/
    └── signature/
        └── v1/
            └── signature.schema.json

Configuration options allow specifying schema versions:

{
  "jacs_agent_schema_version": "v1",
  "jacs_header_schema_version": "v1",
  "jacs_signature_schema_version": "v1"
}

Schema Composition

Document schemas use JSON Schema's allOf to compose the header with type-specific fields:

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "https://hai.ai/schemas/agent/v1/agent.schema.json",
  "allOf": [
    { "$ref": "https://hai.ai/schemas/header/v1/header.schema.json" },
    {
      "type": "object",
      "properties": {
        "jacsAgentType": { ... },
        "jacsServices": { ... }
      }
    }
  ]
}

This ensures all documents share common header fields while allowing type-specific extensions.

Creating Custom Schemas

Create custom schemas that extend JACS schemas:

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "https://example.com/schemas/invoice.schema.json",
  "title": "Invoice",
  "allOf": [
    { "$ref": "https://hai.ai/schemas/header/v1/header.schema.json" },
    {
      "type": "object",
      "properties": {
        "invoiceNumber": {
          "type": "string",
          "description": "Unique invoice identifier"
        },
        "amount": {
          "type": "number",
          "minimum": 0,
          "description": "Invoice amount"
        },
        "currency": {
          "type": "string",
          "enum": ["USD", "EUR", "GBP"],
          "default": "USD"
        },
        "lineItems": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "description": { "type": "string" },
              "quantity": { "type": "integer", "minimum": 1 },
              "unitPrice": { "type": "number", "minimum": 0 }
            },
            "required": ["description", "quantity", "unitPrice"]
          }
        }
      },
      "required": ["invoiceNumber", "amount"]
    }
  ]
}

Validation Rules

Required Fields

All JACS documents require these header fields:

  • jacsId - Unique document identifier (UUID v4)
  • jacsType - Document type identifier
  • jacsVersion - Version identifier (UUID v4)
  • jacsVersionDate - Version timestamp (ISO 8601)
  • jacsOriginalVersion - First version identifier
  • jacsOriginalDate - Creation timestamp
  • jacsLevel - Document level (raw, config, artifact, derived)
  • $schema - Schema reference URL

Format Validation

Fields use JSON Schema format keywords:

  • uuid - UUID v4 format
  • date-time - ISO 8601 date-time format
  • uri - Valid URI format

Enum Constraints

Many fields have enumerated valid values:

{
  "jacsLevel": {
    "enum": ["raw", "config", "artifact", "derived"]
  },
  "jacsAgentType": {
    "enum": ["human", "human-org", "hybrid", "ai"]
  },
  "jacs_agent_key_algorithm": {
    "enum": ["RSA-PSS", "ring-Ed25519", "pq-dilithium", "pq2025"]
  }
}

Schema Reference

For detailed documentation on specific schemas:

See Also

Agent Schema

The Agent Schema defines the structure for agent identity documents in JACS. Agents represent entities that can sign documents, participate in agreements, and provide services.

Schema Location

https://hai.ai/schemas/agent/v1/agent.schema.json

Overview

Agent documents describe:

  • Identity: Unique identifiers and versioning
  • Type: Human, organizational, hybrid, or AI classification
  • Services: Capabilities the agent offers
  • Contacts: How to reach human or hybrid agents
  • Domain: Optional DNS-based verification

Schema Structure

The agent schema extends the Header Schema using JSON Schema composition:

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "https://hai.ai/schemas/agent/v1/agent.schema.json",
  "title": "Agent",
  "description": "General schema for human, hybrid, and AI agents",
  "allOf": [
    { "$ref": "https://hai.ai/schemas/header/v1/header.schema.json" },
    { "type": "object", "properties": { ... } }
  ]
}

Agent Types

The jacsAgentType field classifies the agent:

TypeDescription
humanA biological entity (individual person)
human-orgA group of people (organization, company)
hybridCombination of human and AI components
aiFully artificial intelligence
{
  "jacsAgentType": {
    "type": "string",
    "enum": ["human", "human-org", "hybrid", "ai"]
  }
}

Contact Requirements

Human and hybrid agents must provide contact information:

{
  "if": {
    "properties": {
      "jacsAgentType": { "enum": ["human", "human-org", "hybrid"] }
    }
  },
  "then": {
    "required": ["jacsContacts"]
  }
}

Agent Properties

Core Fields (from Header)

All agents inherit these header fields:

FieldTypeRequiredDescription
jacsIdstring (UUID)YesUnique agent identifier
jacsVersionstring (UUID)YesCurrent version identifier
jacsVersionDatestring (date-time)YesVersion timestamp
jacsTypestringYesSet to "agent"
jacsOriginalVersionstring (UUID)YesFirst version identifier
jacsOriginalDatestring (date-time)YesCreation timestamp
jacsLevelstringYesDocument level
jacsSignatureobjectNoCryptographic signature
jacsSha256stringNoContent hash

Agent-Specific Fields

FieldTypeRequiredDescription
jacsAgentTypestringYesAgent classification
jacsAgentDomainstringNoDomain for DNS verification
jacsServicesarrayYesServices the agent provides
jacsContactsarrayConditionalContact information (required for human/hybrid)

Services

Services describe capabilities the agent offers:

{
  "jacsServices": [{
    "name": "Document Signing Service",
    "serviceDescription": "Sign and verify JACS documents",
    "successDescription": "Documents are signed with valid signatures",
    "failureDescription": "Invalid documents or signing errors",
    "costDescription": "Free for basic usage, paid tiers available",
    "idealCustomerDescription": "Developers building secure agent systems",
    "termsOfService": "https://example.com/tos",
    "privacyPolicy": "https://example.com/privacy",
    "isDev": false,
    "tools": [...]
  }]
}

Service Schema Fields

FieldTypeRequiredDescription
namestringNoService name
serviceDescriptionstringYesWhat the service does
successDescriptionstringYesWhat success looks like
failureDescriptionstringYesWhat failure looks like
costDescriptionstringNoPricing information
idealCustomerDescriptionstringNoTarget customer profile
termsOfServicestringNoLegal terms URL or text
privacyPolicystringNoPrivacy policy URL or text
copyrightstringNoUsage rights for provided data
eulastringNoEnd-user license agreement
isDevbooleanNoWhether this is a dev/test service
toolsarrayNoTool definitions
piiDesiredarrayNoTypes of sensitive data needed

PII Types

Services can declare what personally identifiable information they need:

{
  "piiDesired": ["email", "phone", "address"]
}

Valid PII types:

  • signature - Digital signatures
  • cryptoaddress - Cryptocurrency addresses
  • creditcard - Payment card numbers
  • govid - Government identification
  • social - Social security numbers
  • email - Email addresses
  • phone - Phone numbers
  • address - Physical addresses
  • zip - Postal codes
  • PHI - Protected health information
  • MHI - Mental health information
  • identity - Identity documents
  • political - Political affiliation
  • bankaddress - Banking information
  • income - Income data

Contacts

Contact information for human and hybrid agents:

{
  "jacsContacts": [{
    "firstName": "Jane",
    "lastName": "Smith",
    "email": "jane@example.com",
    "phone": "+1-555-0123",
    "isPrimary": true,
    "mailAddress": "123 Main St",
    "mailState": "CA",
    "mailZip": "94102",
    "mailCountry": "USA"
  }]
}

Contact Schema Fields

FieldTypeDescription
firstNamestringFirst name
lastNamestringLast name
addressNamestringLocation name
phonestringPhone number
emailstring (email)Email address
mailNamestringName at address
mailAddressstringStreet address
mailAddressTwostringAddress line 2
mailStatestringState/province
mailZipstringPostal code
mailCountrystringCountry
isPrimarybooleanPrimary contact flag

DNS Verification

Agents can link to a domain for DNSSEC-validated verification:

{
  "jacsAgentDomain": "example.com"
}

The domain should have a DNS TXT record at _v1.agent.jacs.example.com. containing the agent's public key fingerprint.

See the DNS chapter for complete setup instructions.

Complete Example

AI Agent

{
  "$schema": "https://hai.ai/schemas/agent/v1/agent.schema.json",
  "jacsId": "550e8400-e29b-41d4-a716-446655440000",
  "jacsVersion": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
  "jacsVersionDate": "2024-01-15T10:30:00Z",
  "jacsType": "agent",
  "jacsOriginalVersion": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
  "jacsOriginalDate": "2024-01-15T10:30:00Z",
  "jacsLevel": "artifact",
  "jacsAgentType": "ai",
  "jacsServices": [{
    "name": "Code Review Service",
    "serviceDescription": "Automated code review and analysis",
    "successDescription": "Review completed with actionable feedback",
    "failureDescription": "Unable to process or analyze code",
    "isDev": false
  }],
  "jacsSignature": {
    "agentID": "550e8400-e29b-41d4-a716-446655440000",
    "agentVersion": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
    "date": "2024-01-15T10:30:00Z",
    "signature": "base64-encoded-signature...",
    "publicKeyHash": "sha256-hash-of-public-key",
    "signingAlgorithm": "ring-Ed25519",
    "fields": ["jacsId", "jacsVersion", "jacsAgentType", "jacsServices"]
  },
  "jacsSha256": "document-hash..."
}

Human Agent

{
  "$schema": "https://hai.ai/schemas/agent/v1/agent.schema.json",
  "jacsId": "660e8400-e29b-41d4-a716-446655440001",
  "jacsVersion": "a47ac10b-58cc-4372-a567-0e02b2c3d480",
  "jacsVersionDate": "2024-01-15T11:00:00Z",
  "jacsType": "agent",
  "jacsOriginalVersion": "a47ac10b-58cc-4372-a567-0e02b2c3d480",
  "jacsOriginalDate": "2024-01-15T11:00:00Z",
  "jacsLevel": "artifact",
  "jacsAgentType": "human",
  "jacsAgentDomain": "smith.example.com",
  "jacsServices": [{
    "name": "Consulting",
    "serviceDescription": "Technical consulting services",
    "successDescription": "Project goals achieved",
    "failureDescription": "Unable to meet requirements",
    "termsOfService": "https://smith.example.com/tos"
  }],
  "jacsContacts": [{
    "firstName": "John",
    "lastName": "Smith",
    "email": "john@smith.example.com",
    "isPrimary": true
  }]
}

Organization Agent

{
  "$schema": "https://hai.ai/schemas/agent/v1/agent.schema.json",
  "jacsId": "770e8400-e29b-41d4-a716-446655440002",
  "jacsVersion": "b47ac10b-58cc-4372-a567-0e02b2c3d481",
  "jacsVersionDate": "2024-01-15T12:00:00Z",
  "jacsType": "agent",
  "jacsOriginalVersion": "b47ac10b-58cc-4372-a567-0e02b2c3d481",
  "jacsOriginalDate": "2024-01-15T12:00:00Z",
  "jacsLevel": "artifact",
  "jacsAgentType": "human-org",
  "jacsAgentDomain": "acme.com",
  "jacsServices": [{
    "name": "Enterprise Software",
    "serviceDescription": "Enterprise software solutions",
    "successDescription": "Software deployed and operational",
    "failureDescription": "Deployment or integration failure",
    "privacyPolicy": "https://acme.com/privacy",
    "piiDesired": ["email", "phone"]
  }],
  "jacsContacts": [{
    "addressName": "Acme Corporation",
    "email": "contact@acme.com",
    "phone": "+1-800-555-ACME",
    "mailAddress": "1 Corporate Plaza",
    "mailState": "NY",
    "mailZip": "10001",
    "mailCountry": "USA",
    "isPrimary": true
  }]
}

Creating Agents

Python

import jacs
import json

agent = jacs.JacsAgent()
agent.load('./jacs.config.json')

# The agent is created during configuration setup
# Agent document is available after loading
agent_json = agent.load('./jacs.config.json')
agent_doc = json.loads(agent_json)

print(f"Agent ID: {agent_doc['jacsId']}")
print(f"Agent Type: {agent_doc['jacsAgentType']}")

Node.js

import { JacsAgent } from 'jacsnpm';

const agent = new JacsAgent();
const agentJson = agent.load('./jacs.config.json');
const agentDoc = JSON.parse(agentJson);

console.log(`Agent ID: ${agentDoc.jacsId}`);
console.log(`Agent Type: ${agentDoc.jacsAgentType}`);

CLI

# Create a new agent
jacs agent create

# View agent details
jacs agent show

Verifying Agents

# Verify the loaded agent
is_valid = agent.verify_agent()

# Verify another agent file
is_valid = agent.verify_agent('./other-agent.json')
// Verify the loaded agent
const isValid = agent.verifyAgent();

// Verify another agent file
const isOtherValid = agent.verifyAgent('./other-agent.json');

See Also

Document Schema

The Document Schema (Header Schema) defines the base structure for all JACS documents. Every JACS document type (agents, tasks, messages, etc.) extends this schema.

Schema Location

https://hai.ai/schemas/header/v1/header.schema.json

Overview

The header schema provides:

  • Unique Identification: Every document has a unique ID and version
  • Version Tracking: Full history with previous version references
  • Cryptographic Integrity: Signatures and hashes for verification
  • File Attachments: Support for embedded or linked files
  • Vector Embeddings: Pre-computed embeddings for semantic search
  • Agreements: Multi-party signature support

Core Fields

Identification

FieldTypeRequiredDescription
$schemastringYesSchema URL for validation
jacsIdstring (UUID)YesUnique document identifier
jacsTypestringYesDocument type (agent, task, etc.)
{
  "$schema": "https://hai.ai/schemas/header/v1/header.schema.json",
  "jacsId": "550e8400-e29b-41d4-a716-446655440000",
  "jacsType": "document"
}

Versioning

FieldTypeRequiredDescription
jacsVersionstring (UUID)YesCurrent version identifier
jacsVersionDatestring (date-time)YesVersion creation timestamp
jacsPreviousVersionstring (UUID)NoPrevious version (if not first)
jacsOriginalVersionstring (UUID)YesFirst version identifier
jacsOriginalDatestring (date-time)YesDocument creation timestamp
jacsBranchstring (UUID)NoBranch identifier for JACS databases
{
  "jacsVersion": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
  "jacsVersionDate": "2024-01-15T10:30:00Z",
  "jacsPreviousVersion": "e36ac10b-58cc-4372-a567-0e02b2c3d478",
  "jacsOriginalVersion": "a47ac10b-58cc-4372-a567-0e02b2c3d476",
  "jacsOriginalDate": "2024-01-01T09:00:00Z"
}

Document Level

The jacsLevel field indicates the intended use:

LevelDescription
rawRaw data that should not change
configConfiguration meant to be updated
artifactGenerated content that may be updated
derivedComputed from other documents
{
  "jacsLevel": "artifact"
}

Cryptographic Fields

Signature

The jacsSignature field contains the creator's cryptographic signature:

{
  "jacsSignature": {
    "agentID": "550e8400-e29b-41d4-a716-446655440000",
    "agentVersion": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
    "date": "2024-01-15T10:30:00Z",
    "signature": "base64-encoded-signature-string",
    "publicKeyHash": "sha256-hash-of-public-key",
    "signingAlgorithm": "ring-Ed25519",
    "fields": ["jacsId", "jacsVersion", "jacsType", "content"]
  }
}

Signature Schema Fields

FieldTypeRequiredDescription
agentIDstring (UUID)YesSigning agent's ID
agentVersionstring (UUID)YesSigning agent's version
datestring (date-time)YesSigning timestamp
signaturestringYesBase64-encoded signature
publicKeyHashstringYesHash of public key used
signingAlgorithmstringNoAlgorithm used (ring-Ed25519, RSA-PSS, pq-dilithium)
fieldsarrayYesFields included in signature
responsestringNoText response with signature
responseTypestringNoagree, disagree, or reject

Registration

The jacsRegistration field contains a signature from a registration authority:

{
  "jacsRegistration": {
    "agentID": "registrar-agent-id",
    "agentVersion": "registrar-version",
    "date": "2024-01-15T10:35:00Z",
    "signature": "registrar-signature",
    "publicKeyHash": "registrar-key-hash",
    "signingAlgorithm": "ring-Ed25519",
    "fields": ["jacsId", "jacsSignature"]
  }
}

Hash

The jacsSha256 field contains a SHA-256 hash of all document content (excluding this field):

{
  "jacsSha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
}

Agreements

Documents can include multi-party agreements using jacsAgreement:

{
  "jacsAgreement": {
    "agentIDs": [
      "agent-1-uuid",
      "agent-2-uuid",
      "agent-3-uuid"
    ],
    "question": "Do you agree to these terms?",
    "context": "Q1 2024 Service Agreement",
    "signatures": [
      {
        "agentID": "agent-1-uuid",
        "signature": "...",
        "responseType": "agree",
        "date": "2024-01-15T11:00:00Z"
      }
    ]
  },
  "jacsAgreementHash": "hash-of-content-at-agreement-time"
}

Agreement Schema Fields

FieldTypeRequiredDescription
agentIDsarrayYesRequired signers
questionstringNoWhat parties are agreeing to
contextstringNoAdditional context
signaturesarrayNoCollected signatures

File Attachments

Documents can include file attachments using jacsFiles:

{
  "jacsFiles": [
    {
      "mimetype": "application/pdf",
      "path": "./documents/contract.pdf",
      "embed": true,
      "contents": "base64-encoded-file-contents",
      "sha256": "file-content-hash"
    },
    {
      "mimetype": "image/png",
      "path": "./images/diagram.png",
      "embed": false,
      "sha256": "file-content-hash"
    }
  ]
}

File Schema Fields

FieldTypeRequiredDescription
mimetypestringYesMIME type of the file
pathstringYesFile location (local path)
embedbooleanYesWhether to embed contents
contentsstringNoBase64-encoded file contents
sha256stringNoHash for content verification

Vector Embeddings

Documents can include pre-computed embeddings for semantic search:

{
  "jacsEmbedding": [
    {
      "llm": "text-embedding-ada-002",
      "vector": [0.0023, -0.0089, 0.0156, ...]
    }
  ]
}

Embedding Schema Fields

FieldTypeRequiredDescription
llmstringYesModel used for embedding
vectorarrayYesVector of numbers

Complete Example

{
  "$schema": "https://hai.ai/schemas/header/v1/header.schema.json",
  "jacsId": "550e8400-e29b-41d4-a716-446655440000",
  "jacsType": "document",
  "jacsVersion": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
  "jacsVersionDate": "2024-01-15T10:30:00Z",
  "jacsOriginalVersion": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
  "jacsOriginalDate": "2024-01-15T10:30:00Z",
  "jacsLevel": "artifact",

  "title": "Sample Document",
  "content": "This is the document content.",

  "jacsFiles": [
    {
      "mimetype": "application/pdf",
      "path": "./attachment.pdf",
      "embed": false,
      "sha256": "abc123..."
    }
  ],

  "jacsSignature": {
    "agentID": "550e8400-e29b-41d4-a716-446655440000",
    "agentVersion": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
    "date": "2024-01-15T10:30:00Z",
    "signature": "signature-base64...",
    "publicKeyHash": "key-hash...",
    "signingAlgorithm": "ring-Ed25519",
    "fields": ["jacsId", "jacsVersion", "title", "content"]
  },

  "jacsSha256": "document-hash..."
}

HAI Field Categories

Fields include a hai property indicating their category:

CategoryDescriptionExamples
metaMetadata (IDs, dates)jacsId, jacsVersion, jacsVersionDate
baseCryptographic datajacsSha256, signature
agentAgent-controlled contentCustom content fields

This categorization determines which fields are included in hash and signature calculations.

Working with Documents

Creating Documents

import jacs
import json

agent = jacs.JacsAgent()
agent.load('./jacs.config.json')

# Create a basic document
doc = agent.create_document(json.dumps({
    'title': 'My Document',
    'content': 'Document content here'
}))
import { JacsAgent } from 'jacsnpm';

const agent = new JacsAgent();
agent.load('./jacs.config.json');

const doc = agent.createDocument(JSON.stringify({
  title: 'My Document',
  content: 'Document content here'
}));

Verifying Documents

is_valid = agent.verify_document(doc_json)
const isValid = agent.verifyDocument(docJson);

Updating Documents

doc = json.loads(signed_doc)
document_key = f"{doc['jacsId']}:{doc['jacsVersion']}"

updated = agent.update_document(
    document_key,
    json.dumps({**doc, 'content': 'Updated content'})
)
const doc = JSON.parse(signedDoc);
const documentKey = `${doc.jacsId}:${doc.jacsVersion}`;

const updated = agent.updateDocument(
  documentKey,
  JSON.stringify({...doc, content: 'Updated content'})
);

Adding Attachments

doc = agent.create_document(
    json.dumps({'title': 'Report'}),
    attachments='./report.pdf',
    embed=True
)
const doc = agent.createDocument(
  JSON.stringify({ title: 'Report' }),
  null,    // custom_schema
  null,    // output_filename
  false,   // no_save
  './report.pdf',  // attachments
  true     // embed
);

Version History

Documents maintain a version chain:

Original (v1) ← Previous (v2) ← Current (v3)
     │
     └── jacsOriginalVersion points here for all versions

Each version:

  • Has its own jacsVersion UUID
  • References jacsPreviousVersion (except the first)
  • All share the same jacsId and jacsOriginalVersion

See Also

Task Schema

The Task Schema defines the structure for task documents in JACS. Tasks represent work items with defined states, assigned agents, and completion criteria.

Schema Location

https://hai.ai/schemas/task/v1/task.schema.json

Overview

Task documents manage:

  • Workflow States: From creation through completion
  • Agent Assignment: Customer and assigned agent tracking
  • Actions: Desired outcomes and completion criteria
  • Agreements: Start and end agreements between parties
  • Relationships: Sub-tasks, copies, and merges

Schema Structure

The task schema extends the Header Schema:

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "https://hai.ai/schemas/task/v1/task-schema.json",
  "title": "Task",
  "description": "General schema for stateful resources.",
  "allOf": [
    { "$ref": "https://hai.ai/schemas/header/v1/header.schema.json" },
    { "type": "object", "properties": { ... } }
  ]
}

Task States

Tasks progress through defined workflow states:

StateDescription
creatingTask is being drafted
rfpRequest for proposal - seeking agents
proposalAgent has submitted a proposal
negotiationTerms being negotiated
startedWork has begun
reviewWork submitted for review
completedTask is finished
{
  "jacsTaskState": "started"
}

State Transitions

creating → rfp → proposal → negotiation → started → review → completed
                    ↑_______________|
                (may cycle back for renegotiation)

Task Properties

Core Fields (from Header)

Tasks inherit all document header fields plus task-specific fields.

Task-Specific Fields

FieldTypeRequiredDescription
jacsTaskNamestringNoHuman-readable task name
jacsTaskSuccessstringNoDescription of success criteria
jacsTaskStatestringYesCurrent workflow state
jacsTaskCustomerobjectYesCustomer agent signature
jacsTaskAgentobjectNoAssigned agent signature
jacsTaskStartDatestring (date-time)NoWhen work started
jacsTaskCompleteDatestring (date-time)NoWhen work completed
jacsTaskActionsDesiredarrayYesRequired actions
jacsStartAgreementobjectNoAgreement to begin work
jacsEndAgreementobjectNoAgreement that work is complete

Relationship Fields

FieldTypeDescription
jacsTaskSubTaskOfarrayParent task IDs
jacsTaskCopyOfarraySource task IDs (branching)
jacsTaskMergedTasksarrayTasks folded into this one

Actions

Actions define what needs to be accomplished:

{
  "jacsTaskActionsDesired": [
    {
      "name": "Create API Endpoint",
      "description": "Build REST endpoint for user registration",
      "cost": {
        "value": 500,
        "unit": "USD"
      },
      "duration": {
        "value": 8,
        "unit": "hours"
      },
      "completionAgreementRequired": true,
      "tools": [...]
    }
  ]
}

Action Schema Fields

FieldTypeRequiredDescription
namestringYesAction name
descriptionstringYesWhat needs to be done
toolsarrayNoTools that can be used
costobjectNoCost estimate
durationobjectNoTime estimate
completionAgreementRequiredbooleanNoRequires sign-off

Unit Schema

Costs and durations use the unit schema:

{
  "cost": {
    "value": 100,
    "unit": "USD"
  },
  "duration": {
    "value": 2,
    "unit": "days"
  }
}

Agreements

Tasks can include start and end agreements:

Start Agreement

Signed when parties agree to begin work:

{
  "jacsStartAgreement": {
    "agentIDs": ["customer-uuid", "agent-uuid"],
    "question": "Do you agree to begin this work?",
    "context": "Project XYZ - Phase 1",
    "signatures": [...]
  }
}

End Agreement

Signed when parties agree work is complete:

{
  "jacsEndAgreement": {
    "agentIDs": ["customer-uuid", "agent-uuid"],
    "question": "Do you agree this work is complete?",
    "context": "Final deliverables reviewed",
    "signatures": [...]
  }
}

Complete Example

{
  "$schema": "https://hai.ai/schemas/task/v1/task.schema.json",
  "jacsId": "550e8400-e29b-41d4-a716-446655440000",
  "jacsType": "task",
  "jacsVersion": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
  "jacsVersionDate": "2024-01-15T10:30:00Z",
  "jacsOriginalVersion": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
  "jacsOriginalDate": "2024-01-15T10:30:00Z",
  "jacsLevel": "artifact",

  "jacsTaskName": "Build Authentication System",
  "jacsTaskSuccess": "Users can register, login, and manage sessions",
  "jacsTaskState": "started",

  "jacsTaskCustomer": {
    "agentID": "customer-agent-uuid",
    "agentVersion": "customer-version-uuid",
    "date": "2024-01-15T10:30:00Z",
    "signature": "customer-signature...",
    "publicKeyHash": "customer-key-hash",
    "signingAlgorithm": "ring-Ed25519",
    "fields": ["jacsTaskName", "jacsTaskActionsDesired"]
  },

  "jacsTaskAgent": {
    "agentID": "assigned-agent-uuid",
    "agentVersion": "agent-version-uuid",
    "date": "2024-01-16T09:00:00Z",
    "signature": "agent-signature...",
    "publicKeyHash": "agent-key-hash",
    "signingAlgorithm": "ring-Ed25519",
    "fields": ["jacsTaskName", "jacsTaskActionsDesired"]
  },

  "jacsTaskStartDate": "2024-01-16T09:00:00Z",

  "jacsStartAgreement": {
    "agentIDs": ["customer-agent-uuid", "assigned-agent-uuid"],
    "question": "Do you agree to begin work on this task?",
    "signatures": [
      {
        "agentID": "customer-agent-uuid",
        "signature": "...",
        "responseType": "agree",
        "date": "2024-01-16T09:00:00Z"
      },
      {
        "agentID": "assigned-agent-uuid",
        "signature": "...",
        "responseType": "agree",
        "date": "2024-01-16T09:05:00Z"
      }
    ]
  },

  "jacsTaskActionsDesired": [
    {
      "name": "User Registration",
      "description": "Implement user registration with email verification",
      "duration": { "value": 4, "unit": "hours" },
      "completionAgreementRequired": true
    },
    {
      "name": "User Login",
      "description": "Implement secure login with password hashing",
      "duration": { "value": 3, "unit": "hours" },
      "completionAgreementRequired": true
    },
    {
      "name": "Session Management",
      "description": "Implement JWT-based session tokens",
      "duration": { "value": 2, "unit": "hours" },
      "completionAgreementRequired": false
    }
  ],

  "jacsSignature": {
    "agentID": "customer-agent-uuid",
    "agentVersion": "customer-version-uuid",
    "date": "2024-01-15T10:30:00Z",
    "signature": "document-signature...",
    "publicKeyHash": "key-hash...",
    "signingAlgorithm": "ring-Ed25519",
    "fields": ["jacsId", "jacsTaskName", "jacsTaskActionsDesired"]
  }
}

Task Relationships

Sub-Tasks

Break large tasks into smaller units:

{
  "jacsTaskSubTaskOf": ["parent-task-uuid"]
}

Task Copies (Branching)

Create variations or branches:

{
  "jacsTaskCopyOf": ["original-task-uuid"]
}

Merged Tasks

Combine completed tasks:

{
  "jacsTaskMergedTasks": [
    "subtask-1-uuid",
    "subtask-2-uuid"
  ]
}

Task Workflow

1. Creating a Task

import jacs
import json

agent = jacs.JacsAgent()
agent.load('./jacs.config.json')

task = agent.create_document(json.dumps({
    'jacsTaskName': 'Build Feature X',
    'jacsTaskSuccess': 'Feature is deployed and tested',
    'jacsTaskState': 'creating',
    'jacsTaskActionsDesired': [
        {
            'name': 'Implementation',
            'description': 'Write the code',
            'completionAgreementRequired': True
        }
    ]
}), custom_schema='https://hai.ai/schemas/task/v1/task.schema.json')

2. Assigning an Agent

When an agent accepts the task, add their signature to jacsTaskAgent and update state to started.

3. Signing Start Agreement

Both parties sign the start agreement to confirm work begins.

4. Completing Work

Update state to review, then both parties sign the end agreement.

5. Final Completion

After end agreement is signed by all parties, update state to completed.

State Machine Rules

Current StateValid Next States
creatingrfp
rfpproposal, creating
proposalnegotiation, rfp
negotiationstarted, proposal
startedreview
reviewcompleted, started
completed(terminal)

See Also

Configuration

The JACS configuration file (jacs.config.json) defines agent settings, key locations, storage backends, and observability options.

Schema Location

https://hai.ai/schemas/jacs.config.schema.json

Quick Start

Create a minimal configuration file:

{
  "$schema": "https://hai.ai/schemas/jacs.config.schema.json",
  "jacs_data_directory": "./jacs_data",
  "jacs_key_directory": "./jacs_keys",
  "jacs_agent_private_key_filename": "private.pem",
  "jacs_agent_public_key_filename": "public.pem",
  "jacs_agent_key_algorithm": "ring-Ed25519",
  "jacs_default_storage": "fs"
}

Required Fields

FieldTypeDescription
jacs_data_directorystringPath to store documents and agents
jacs_key_directorystringPath to store cryptographic keys
jacs_agent_private_key_filenamestringPrivate key filename
jacs_agent_public_key_filenamestringPublic key filename
jacs_agent_key_algorithmstringSigning algorithm
jacs_default_storagestringStorage backend

Configuration Options

Key Configuration

jacs_agent_key_algorithm

Specifies the cryptographic algorithm for signing:

ValueDescription
ring-Ed25519Ed25519 signatures (recommended)
RSA-PSSRSA with PSS padding
pq-dilithiumPost-quantum Dilithium
pq2025Post-quantum composite
{
  "jacs_agent_key_algorithm": "ring-Ed25519"
}

jacs_agent_private_key_filename

Name of the private key file in the key directory:

{
  "jacs_agent_private_key_filename": "private.pem"
}

If the key is encrypted, it will have .enc appended automatically when loading.

jacs_agent_public_key_filename

Name of the public key file:

{
  "jacs_agent_public_key_filename": "public.pem"
}

jacs_private_key_password

Password for encrypted private keys:

{
  "jacs_private_key_password": "your-password"
}

Warning: Do not store passwords in config files for production. Use the JACS_AGENT_PRIVATE_KEY_PASSWORD environment variable instead.

Storage Configuration

jacs_default_storage

Specifies where documents are stored:

ValueDescription
fsLocal filesystem
awsAWS S3 storage
haiHAI cloud storage
{
  "jacs_default_storage": "fs"
}

jacs_data_directory

Path for storing documents and agents:

{
  "jacs_data_directory": "./jacs_data"
}

Agent Identity

jacs_agent_id_and_version

Load an existing agent by ID and version:

{
  "jacs_agent_id_and_version": "550e8400-e29b-41d4-a716-446655440000:f47ac10b-58cc-4372-a567-0e02b2c3d479"
}

Schema Versions

Specify which schema versions to use:

{
  "jacs_agent_schema_version": "v1",
  "jacs_header_schema_version": "v1",
  "jacs_signature_schema_version": "v1"
}

DNS Configuration

For DNSSEC-based agent verification:

jacs_agent_domain

Domain for DNS-based public key verification:

{
  "jacs_agent_domain": "example.com"
}

jacs_dns_validate

Enable DNS TXT fingerprint validation:

{
  "jacs_dns_validate": true
}

jacs_dns_strict

Require DNSSEC validation (no fallback):

{
  "jacs_dns_strict": true
}

jacs_dns_required

Require domain and DNS validation:

{
  "jacs_dns_required": true
}

Security

jacs_use_security

Enable strict security features:

{
  "jacs_use_security": "1"
}

Values: "0", "1", or "false", "true"

Observability Configuration

JACS supports comprehensive observability through logs, metrics, and tracing.

Logs Configuration

{
  "observability": {
    "logs": {
      "enabled": true,
      "level": "info",
      "destination": {
        "type": "stderr"
      }
    }
  }
}

Log Levels

LevelDescription
traceMost verbose
debugDebug information
infoGeneral information
warnWarnings
errorErrors only

Log Destinations

stderr (default):

{
  "destination": { "type": "stderr" }
}

File:

{
  "destination": {
    "type": "file",
    "path": "/var/log/jacs/app.log"
  }
}

OTLP (OpenTelemetry):

{
  "destination": {
    "type": "otlp",
    "endpoint": "http://localhost:4317",
    "headers": {
      "Authorization": "Bearer token"
    }
  }
}

Null (disabled):

{
  "destination": { "type": "null" }
}

Metrics Configuration

{
  "observability": {
    "metrics": {
      "enabled": true,
      "destination": {
        "type": "prometheus",
        "endpoint": "http://localhost:9090/api/v1/write"
      },
      "export_interval_seconds": 60
    }
  }
}

Metrics Destinations

Prometheus:

{
  "destination": {
    "type": "prometheus",
    "endpoint": "http://localhost:9090/api/v1/write"
  }
}

OTLP:

{
  "destination": {
    "type": "otlp",
    "endpoint": "http://localhost:4317"
  }
}

File:

{
  "destination": {
    "type": "file",
    "path": "/var/log/jacs/metrics.json"
  }
}

stdout:

{
  "destination": { "type": "stdout" }
}

Tracing Configuration

{
  "observability": {
    "tracing": {
      "enabled": true,
      "sampling": {
        "ratio": 0.1,
        "parent_based": true,
        "rate_limit": 100
      },
      "resource": {
        "service_name": "my-jacs-agent",
        "service_version": "1.0.0",
        "environment": "production",
        "attributes": {
          "team": "backend"
        }
      }
    }
  }
}

Sampling Options

FieldTypeDescription
rationumber (0-1)Percentage of traces to sample
parent_basedbooleanFollow parent span's sampling decision
rate_limitintegerMax traces per second

Complete Configuration Example

{
  "$schema": "https://hai.ai/schemas/jacs.config.schema.json",

  "jacs_data_directory": "./jacs_data",
  "jacs_key_directory": "./jacs_keys",
  "jacs_agent_private_key_filename": "private.pem",
  "jacs_agent_public_key_filename": "public.pem",
  "jacs_agent_key_algorithm": "ring-Ed25519",
  "jacs_default_storage": "fs",

  "jacs_agent_schema_version": "v1",
  "jacs_header_schema_version": "v1",
  "jacs_signature_schema_version": "v1",

  "jacs_agent_domain": "myagent.example.com",
  "jacs_dns_validate": true,
  "jacs_dns_strict": false,

  "observability": {
    "logs": {
      "enabled": true,
      "level": "info",
      "destination": {
        "type": "file",
        "path": "/var/log/jacs/agent.log"
      }
    },
    "metrics": {
      "enabled": true,
      "destination": {
        "type": "prometheus",
        "endpoint": "http://prometheus:9090/api/v1/write"
      },
      "export_interval_seconds": 30
    },
    "tracing": {
      "enabled": true,
      "sampling": {
        "ratio": 0.1,
        "parent_based": true
      },
      "resource": {
        "service_name": "jacs-agent",
        "service_version": "1.0.0",
        "environment": "production"
      }
    }
  }
}

Environment Variables

Configuration can be overridden with environment variables:

VariableConfig Field
JACS_AGENT_PRIVATE_KEY_PASSWORDjacs_private_key_password
JACS_DATA_DIRECTORYjacs_data_directory
JACS_KEY_DIRECTORYjacs_key_directory
export JACS_AGENT_PRIVATE_KEY_PASSWORD="secure-password"

Loading Configuration

Python

import jacs

agent = jacs.JacsAgent()
agent.load('./jacs.config.json')

Node.js

import { JacsAgent } from 'jacsnpm';

const agent = new JacsAgent();
agent.load('./jacs.config.json');

CLI

jacs --config ./jacs.config.json agent show

Production Best Practices

  1. Never commit private keys - Keep keys out of version control
  2. Use environment variables for secrets - Don't store passwords in config files
  3. Enable observability - Configure logs and metrics for monitoring
  4. Use DNS validation - Enable jacs_dns_validate for additional security
  5. Secure key directories - Restrict file permissions on key directories
chmod 700 ./jacs_keys
chmod 600 ./jacs_keys/private.pem

See Also

Security Model

JACS implements a comprehensive security model designed to ensure authenticity, integrity, and non-repudiation for all agent communications and documents.

Core Security Principles

1. Cryptographic Identity

Every JACS agent has a unique cryptographic identity:

  • Key Pair: Each agent possesses a private/public key pair
  • Agent ID: Unique UUID identifying the agent
  • Public Key Hash: SHA-256 hash of the public key for verification
{
  "jacsSignature": {
    "agentID": "550e8400-e29b-41d4-a716-446655440000",
    "publicKeyHash": "sha256-of-public-key",
    "signingAlgorithm": "ring-Ed25519"
  }
}

2. Document Integrity

All documents include cryptographic guarantees:

  • Signature: Cryptographic signature over specified fields
  • Hash: SHA-256 hash of document contents
  • Version Tracking: Immutable version history

3. Non-Repudiation

Signatures provide proof of origin:

  • Agents cannot deny signing a document
  • Timestamps record when signatures were made
  • Public keys enable independent verification

Threat Model

Protected Against

ThreatProtection
TamperingContent hashes detect modifications
ImpersonationCryptographic signatures verify identity
Replay AttacksTimestamps and version IDs ensure freshness
Man-in-the-MiddleDNS verification via DNSSEC
Key CompromiseKey rotation through versioning

Trust Assumptions

  1. Private keys are kept secure
  2. Cryptographic algorithms are sound
  3. DNS infrastructure (when used) is trustworthy

Signature Process

Signing a Document

  1. Field Selection: Determine which fields to sign
  2. Canonicalization: Serialize fields deterministically
  3. Signature Generation: Sign with private key
  4. Hash Computation: Compute SHA-256 of signed document
import jacs
import json

agent = jacs.JacsAgent()
agent.load('./jacs.config.json')

# Create signed document
doc = agent.create_document(json.dumps({
    'title': 'Confidential Report',
    'content': 'Sensitive data here'
}))

# Document now includes jacsSignature and jacsSha256

Verifying a Document

  1. Hash Verification: Recompute hash and compare
  2. Signature Verification: Verify signature with public key
  3. Agent Verification: Optionally verify agent identity via DNS
is_valid = agent.verify_document(doc_json)
is_signature_valid = agent.verify_signature(doc_json)

Key Management

Key Generation

JACS generates cryptographic key pairs during agent creation:

# Keys are created in the configured key directory
jacs_keys/
├── private.pem    # Private key (keep secure!)
└── public.pem     # Public key (can be shared)

Key Protection

Encryption at Rest:

{
  "jacs_private_key_password": "NEVER_STORE_IN_CONFIG"
}

Use environment variables instead:

export JACS_AGENT_PRIVATE_KEY_PASSWORD="secure-password"

File Permissions:

chmod 700 ./jacs_keys
chmod 600 ./jacs_keys/private.pem

Key Rotation

Update agent version to rotate keys:

  1. Generate new key pair
  2. Create new agent version
  3. Sign new version with old key
  4. Update configuration to use new keys

DNS-Based Verification

JACS supports DNSSEC-validated identity verification:

How It Works

  1. Agent publishes public key fingerprint in DNS TXT record
  2. Verifier queries DNS for _v1.agent.jacs.<domain>.
  3. DNSSEC validates the response authenticity
  4. Fingerprint is compared against agent's public key

Configuration

{
  "jacs_agent_domain": "myagent.example.com",
  "jacs_dns_validate": true,
  "jacs_dns_strict": true
}

Security Levels

ModeDescription
jacs_dns_validate: falseNo DNS verification
jacs_dns_validate: trueAttempt DNS verification, allow fallback
jacs_dns_strict: trueRequire DNSSEC validation
jacs_dns_required: trueFail if domain not present

Agreement Security

Multi-party agreements provide additional security:

Agreement Structure

{
  "jacsAgreement": {
    "agentIDs": ["agent-1", "agent-2", "agent-3"],
    "signatures": [
      {
        "agentID": "agent-1",
        "signature": "...",
        "responseType": "agree",
        "date": "2024-01-15T10:00:00Z"
      }
    ]
  },
  "jacsAgreementHash": "hash-at-agreement-time"
}

Agreement Guarantees

  1. Content Lock: jacsAgreementHash ensures all parties agreed to same content
  2. Individual Consent: Each signature records explicit agreement
  3. Response Types: Support for agree, disagree, or reject
  4. Timestamp: Records when each party signed

Request/Response Security

For MCP and HTTP communication:

Request Signing

signed_request = agent.sign_request({
    'method': 'tools/call',
    'params': {'name': 'echo', 'arguments': {'text': 'hello'}}
})

The signed request includes:

  • Full JACS document structure
  • Agent signature
  • Timestamp
  • Content hash

Response Verification

result = agent.verify_response(response_string)
payload = result.get('payload')
agent_id = result.get('agentId')  # Who signed the response

Algorithm Security

Supported Algorithms

AlgorithmTypeSecurity Level
ring-Ed25519Elliptic CurveHigh (recommended)
RSA-PSSRSAHigh
pq-dilithiumPost-QuantumQuantum-resistant
pq2025CompositeTransitional

Algorithm Selection

Choose based on requirements:

  • General Use: ring-Ed25519 - fast, secure, small signatures
  • Legacy Systems: RSA-PSS - widely supported
  • Future-Proofing: pq-dilithium - quantum-resistant
  • Transition: pq2025 - hybrid classical/post-quantum

Security Best Practices

1. Key Storage

# Never commit keys to version control
echo "jacs_keys/" >> .gitignore

# Secure file permissions
chmod 700 ./jacs_keys
chmod 600 ./jacs_keys/private.pem

2. Password Handling

# Use environment variables
export JACS_AGENT_PRIVATE_KEY_PASSWORD="$(pass show jacs/key-password)"

3. Transport Security

Always use TLS for network communication:

# HTTPS for web transport
client = JACSMCPClient("https://localhost:8000/sse")  # Good
# client = JACSMCPClient("http://localhost:8000/sse")  # Avoid in production

4. Verification Policies

{
  "jacs_dns_strict": true,
  "jacs_dns_required": true,
  "jacs_use_security": "1"
}

5. Audit Logging

Enable observability for security auditing:

{
  "observability": {
    "logs": {
      "enabled": true,
      "level": "info"
    }
  }
}

Security Checklist

Development

  • Generate unique keys for each environment
  • Never commit private keys
  • Use test keys separate from production

Production

  • Encrypt private keys at rest
  • Use environment variables for secrets
  • Enable DNS verification
  • Configure strict security mode
  • Enable audit logging
  • Use TLS for all network transport
  • Restrict key file permissions
  • Implement key rotation policy

Verification

  • Always verify documents before trusting
  • Verify agent signatures
  • Check agreement completeness
  • Validate DNS records when required

Security Considerations

Supply Chain

  • Verify JACS packages are from official sources
  • Use package checksums
  • Keep dependencies updated

Side Channels

  • Use constant-time comparison for signatures
  • Protect against timing attacks
  • Secure memory handling for keys

Recovery

  • Backup key material securely
  • Document key recovery procedures
  • Plan for key compromise scenarios

See Also

Cryptographic Algorithms

JACS supports multiple cryptographic algorithms for digital signatures, providing flexibility for different security requirements and future-proofing against quantum computing threats.

Supported Algorithms

AlgorithmConfig ValueTypeKey SizeSignature SizeRecommended Use
Ed25519ring-Ed25519Elliptic Curve32 bytes64 bytesGeneral purpose (default)
RSA-PSSRSA-PSSRSA2048-4096 bits256-512 bytesLegacy systems
Dilithiumpq-dilithiumLattice-based~1.3 KB~2.4 KBPost-quantum
PQ2025pq2025Hybrid~1.3 KB~2.5 KBTransitional

Ed25519 (ring-Ed25519)

The recommended algorithm for most use cases.

Overview

Ed25519 is an elliptic curve signature scheme using Curve25519. JACS uses the ring cryptographic library implementation.

Characteristics

  • Speed: Extremely fast signing and verification
  • Key Size: 32-byte private key, 32-byte public key
  • Signature Size: 64 bytes
  • Security Level: ~128 bits (classical)

Configuration

{
  "jacs_agent_key_algorithm": "ring-Ed25519"
}

Use Cases

  • General agent communication
  • MCP message signing
  • HTTP request/response signing
  • Document signing

Example

import jacs
import json

agent = jacs.JacsAgent()
agent.load('./jacs.config.json')  # Using ring-Ed25519

# Sign a message
signature = agent.sign_string("Hello, World!")
print(f"Signature (64 bytes): {len(signature)} characters base64")

RSA-PSS

Industry-standard RSA with Probabilistic Signature Scheme padding.

Overview

RSA-PSS provides compatibility with systems that require RSA signatures. JACS uses 2048-bit or larger keys.

Characteristics

  • Speed: Slower than Ed25519
  • Key Size: 2048-4096 bits
  • Signature Size: Same as key size (256-512 bytes)
  • Security Level: ~112-128 bits (2048-bit key)

Configuration

{
  "jacs_agent_key_algorithm": "RSA-PSS"
}

Use Cases

  • Integration with legacy systems
  • Compliance requirements mandating RSA
  • Interoperability with enterprise PKI

Considerations

  • Larger signatures increase document size
  • Slower than Ed25519
  • Larger keys needed for equivalent security

Dilithium (pq-dilithium)

NIST-standardized post-quantum digital signature algorithm.

Overview

Dilithium is a lattice-based signature scheme selected by NIST for post-quantum cryptography standardization. It provides security against both classical and quantum computers.

Characteristics

  • Speed: Moderate (faster than RSA, slower than Ed25519)
  • Key Size: ~1.3 KB public key, ~2.5 KB private key
  • Signature Size: ~2.4 KB
  • Security Level: NIST Level 3 (quantum-resistant)

Configuration

{
  "jacs_agent_key_algorithm": "pq-dilithium"
}

Use Cases

  • Long-term document security
  • Protection against future quantum attacks
  • High-security applications
  • Government/defense requirements

Considerations

  • Larger signatures and keys than classical algorithms
  • Newer algorithm (less battle-tested)
  • May be required for future compliance

PQ2025 (Hybrid)

Transitional hybrid scheme combining classical and post-quantum algorithms.

Overview

PQ2025 combines Ed25519 with Dilithium, providing security even if one algorithm is broken. This approach is recommended by security researchers during the quantum transition period.

Characteristics

  • Speed: Slower (two signatures computed)
  • Key Size: Combined Ed25519 + Dilithium
  • Signature Size: ~2.5 KB (combined)
  • Security Level: Max of both algorithms

Configuration

{
  "jacs_agent_key_algorithm": "pq2025"
}

Use Cases

  • Transitioning to post-quantum
  • Maximum security requirements
  • Uncertainty about algorithm security
  • Long-lived documents

Considerations

  • Largest signatures
  • Slowest signing/verification
  • Best for paranoid security requirements

Algorithm Selection Guide

Decision Matrix

RequirementRecommended Algorithm
Best performancering-Ed25519
Smallest signaturesring-Ed25519
Legacy compatibilityRSA-PSS
Quantum resistancepq-dilithium
Maximum securitypq2025
General purposering-Ed25519

By Use Case

Web APIs and MCP:

{
  "jacs_agent_key_algorithm": "ring-Ed25519"
}

Fast signing is critical for real-time communication.

Legal/Financial Documents:

{
  "jacs_agent_key_algorithm": "pq-dilithium"
}

Long-term validity requires quantum resistance.

Enterprise Integration:

{
  "jacs_agent_key_algorithm": "RSA-PSS"
}

Compatibility with existing PKI infrastructure.

High-Security:

{
  "jacs_agent_key_algorithm": "pq2025"
}

Belt-and-suspenders approach for maximum protection.

Key Generation

Keys are generated automatically when creating an agent:

# Directory structure after agent creation
jacs_keys/
├── private.pem    # Algorithm-specific private key
└── public.pem     # Algorithm-specific public key

Key Formats

AlgorithmPrivate Key FormatPublic Key Format
ring-Ed25519PEM (PKCS#8)PEM (SPKI)
RSA-PSSPEM (PKCS#8)PEM (SPKI)
pq-dilithiumPEM (custom)PEM (custom)
pq2025PEM (combined)PEM (combined)

Signature Structure

Signatures in JACS documents include algorithm metadata:

{
  "jacsSignature": {
    "agentID": "550e8400-e29b-41d4-a716-446655440000",
    "agentVersion": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
    "date": "2024-01-15T10:30:00Z",
    "signature": "base64-encoded-signature",
    "publicKeyHash": "sha256-of-public-key",
    "signingAlgorithm": "ring-Ed25519",
    "fields": ["jacsId", "jacsVersion", "content"]
  }
}

The signingAlgorithm field enables verifiers to use the correct verification method.

Hashing

JACS uses SHA-256 for all hash operations:

  • Document content hashing (jacsSha256)
  • Public key fingerprints (publicKeyHash)
  • Agreement content locking (jacsAgreementHash)
{
  "jacsSha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
}

Algorithm Migration

To migrate to a new algorithm:

  1. Generate New Keys

    {
      "jacs_agent_key_algorithm": "pq-dilithium"
    }
    
  2. Create New Agent Version

    # Load with old algorithm
    agent.load('./old-config.json')
    
    # Update to new algorithm and generate new version
    new_agent = agent.update_agent(json.dumps({
        # ... agent data with new keys
    }))
    
  3. Update Configuration

    {
      "jacs_agent_id_and_version": "agent-id:new-version",
      "jacs_agent_key_algorithm": "pq-dilithium"
    }
    
  4. Maintain Backward Compatibility

    • Keep old agent versions for verifying old documents
    • Old signatures remain valid with old public keys

Performance Comparison

Approximate performance (varies by hardware):

AlgorithmSign (ops/sec)Verify (ops/sec)Key Gen (ms)
ring-Ed25519~50,000~20,000<1
RSA-PSS (2048)~1,000~30,000~100
pq-dilithium~5,000~10,000~1
pq2025~4,000~8,000~2

Security Considerations

Algorithm Agility

JACS documents include the signing algorithm, enabling:

  • Verification with correct algorithm
  • Graceful algorithm transitions
  • Multi-algorithm environments

Forward Secrecy

Signatures don't provide forward secrecy. For confidentiality:

  • Use TLS for transport
  • Consider additional encryption layers

Key Compromise

If a private key is compromised:

  1. Generate new key pair
  2. Create new agent version
  3. Revoke trust in compromised version
  4. Re-sign critical documents

See Also

Storage Backends

JACS supports multiple storage backends for persisting documents and agents. This flexibility allows deployment in various environments from local development to cloud infrastructure.

Available Backends

BackendConfig ValueDescription
FilesystemfsLocal file storage (default)
AWS S3awsAmazon S3 object storage
HAI CloudhaiHAI managed storage

Configuration

Set the storage backend in your configuration:

{
  "jacs_default_storage": "fs",
  "jacs_data_directory": "./jacs_data"
}

Filesystem Storage (fs)

The default storage backend, storing documents as JSON files on the local filesystem.

Configuration

{
  "jacs_default_storage": "fs",
  "jacs_data_directory": "./jacs_data"
}

Directory Structure

jacs_data/
├── agents/
│   └── {agent-id}/
│       └── {version-id}.json
├── documents/
│   └── {document-id}/
│       └── {version-id}.json
└── files/
    └── {attachment-hash}

Use Cases

  • Local development
  • Single-server deployments
  • Testing and prototyping
  • Air-gapped environments

Advantages

  • Simple setup
  • No network dependencies
  • Fast local access
  • Easy backup and migration

Considerations

  • Not suitable for distributed systems
  • Limited by local disk space
  • Single point of failure

Example

import jacs
import json

agent = jacs.JacsAgent()
agent.load('./jacs.config.json')  # Using filesystem storage

# Documents are saved to jacs_data/documents/
doc = agent.create_document(json.dumps({
    'title': 'My Document'
}))
# Saved to: jacs_data/documents/{doc-id}/{version-id}.json

AWS S3 Storage (aws)

Cloud object storage using Amazon S3.

Configuration

{
  "jacs_default_storage": "aws",
  "jacs_data_directory": "s3://my-jacs-bucket/data"
}

Environment Variables

export AWS_ACCESS_KEY_ID="your-access-key"
export AWS_SECRET_ACCESS_KEY="your-secret-key"
export AWS_REGION="us-east-1"

Bucket Structure

my-jacs-bucket/
├── data/
│   ├── agents/
│   │   └── {agent-id}/
│   │       └── {version-id}.json
│   ├── documents/
│   │   └── {document-id}/
│   │       └── {version-id}.json
│   └── files/
│       └── {attachment-hash}

Use Cases

  • Production deployments
  • Distributed systems
  • High availability requirements
  • Large document volumes

Advantages

  • Scalable storage
  • High durability (99.999999999%)
  • Geographic redundancy
  • Built-in versioning support

Considerations

  • Requires AWS account
  • Network latency
  • Storage costs
  • IAM configuration needed

IAM Policy

Minimum required permissions:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:GetObject",
        "s3:PutObject",
        "s3:DeleteObject",
        "s3:ListBucket"
      ],
      "Resource": [
        "arn:aws:s3:::my-jacs-bucket",
        "arn:aws:s3:::my-jacs-bucket/*"
      ]
    }
  ]
}

Example

import jacs
import json
import os

# Set AWS credentials
os.environ['AWS_ACCESS_KEY_ID'] = 'your-key'
os.environ['AWS_SECRET_ACCESS_KEY'] = 'your-secret'
os.environ['AWS_REGION'] = 'us-east-1'

agent = jacs.JacsAgent()
agent.load('./jacs.s3.config.json')

# Documents are saved to S3
doc = agent.create_document(json.dumps({
    'title': 'Cloud Document'
}))

HAI Cloud Storage (hai)

Managed storage provided by HAI.

Configuration

{
  "jacs_default_storage": "hai"
}

Features

  • Managed infrastructure
  • Built-in agent registry
  • Cross-organization document sharing
  • Integrated DNS verification

Use Cases

  • Multi-agent ecosystems
  • Cross-organization collaboration
  • Managed deployments
  • Integration with HAI services

In-Memory Storage

For testing and temporary operations, documents can be created without saving:

# Create document without saving
doc = agent.create_document(
    json.dumps({'temp': 'data'}),
    no_save=True  # Don't persist
)
const doc = agent.createDocument(
  JSON.stringify({ temp: 'data' }),
  null,  // custom_schema
  null,  // output_filename
  true   // no_save = true
);

Storage Selection Guide

ScenarioRecommended Backend
Developmentfs
Single serverfs
Cloud deploymentaws
High availabilityaws
Multi-organizationhai
TestingIn-memory (no_save)

File Storage

Embedded Files

Files can be embedded directly in documents:

doc = agent.create_document(
    json.dumps({'report': 'Monthly Report'}),
    attachments='./report.pdf',
    embed=True
)

The file contents are base64-encoded and stored in jacsFiles.

External Files

Or reference files without embedding:

doc = agent.create_document(
    json.dumps({'report': 'Monthly Report'}),
    attachments='./report.pdf',
    embed=False
)

Only the file path and hash are stored. The file must be available when the document is accessed.

Data Migration

Filesystem to S3

import jacs
import json
import os

# Load from filesystem
fs_agent = jacs.JacsAgent()
fs_agent.load('./jacs.fs.config.json')

# Read all documents
# (implementation depends on your document tracking)

# Configure S3
s3_agent = jacs.JacsAgent()
s3_agent.load('./jacs.s3.config.json')

# Re-create documents in S3
for doc_json in documents:
    doc = json.loads(doc_json)
    # Remove existing signatures for re-signing
    del doc['jacsSignature']
    del doc['jacsSha256']
    s3_agent.create_document(json.dumps(doc))

Export/Import

For manual migration:

# Export from filesystem
tar -czf jacs_backup.tar.gz ./jacs_data

# Import to new location
tar -xzf jacs_backup.tar.gz -C /new/location

Backup and Recovery

Filesystem Backup

# Regular backups
rsync -av ./jacs_data/ /backup/jacs_data/

# Or with timestamp
tar -czf jacs_backup_$(date +%Y%m%d).tar.gz ./jacs_data

S3 Backup

Enable S3 versioning for automatic backups:

aws s3api put-bucket-versioning \
    --bucket my-jacs-bucket \
    --versioning-configuration Status=Enabled

Cross-Region Replication

For disaster recovery with S3:

aws s3api put-bucket-replication \
    --bucket my-jacs-bucket \
    --replication-configuration file://replication.json

Performance Optimization

Filesystem

  • Use SSD storage
  • Consider separate disk for jacs_data
  • Enable filesystem caching

S3

  • Use regional endpoints
  • Enable transfer acceleration for global access
  • Consider S3 Intelligent-Tiering for cost optimization

Caching

Implement application-level caching for frequently accessed documents:

import functools

@functools.lru_cache(maxsize=100)
def get_document(doc_id, version_id):
    return agent.get_document(f"{doc_id}:{version_id}")

Security Considerations

Filesystem

# Restrict permissions
chmod 700 ./jacs_data
chmod 600 ./jacs_data/**/*.json

S3

  • Enable encryption at rest (SSE-S3 or SSE-KMS)
  • Use VPC endpoints for private access
  • Enable access logging
  • Configure bucket policies carefully
{
  "jacs_data_directory": "s3://my-jacs-bucket/data",
  "aws_s3_encryption": "AES256"
}

See Also

Custom Schemas

JACS allows you to define custom document schemas that extend the base header schema, enabling type-safe, validated documents for your specific use cases.

Overview

Custom schemas:

  • Inherit all JACS header fields (jacsId, jacsVersion, jacsSignature, etc.)
  • Add domain-specific fields with validation
  • Enable IDE autocompletion and type checking
  • Ensure document consistency across your application

Creating a Custom Schema

Basic Structure

Custom schemas extend the JACS header using allOf:

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "https://example.com/schemas/invoice.schema.json",
  "title": "Invoice",
  "description": "Invoice document with JACS signing",
  "allOf": [
    { "$ref": "https://hai.ai/schemas/header/v1/header.schema.json" },
    {
      "type": "object",
      "properties": {
        "invoiceNumber": {
          "type": "string",
          "description": "Unique invoice identifier"
        },
        "amount": {
          "type": "number",
          "minimum": 0
        },
        "currency": {
          "type": "string",
          "enum": ["USD", "EUR", "GBP"]
        }
      },
      "required": ["invoiceNumber", "amount"]
    }
  ]
}

Step-by-Step Guide

  1. Create the schema file
// schemas/order.schema.json
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "https://mycompany.com/schemas/order.schema.json",
  "title": "Order",
  "description": "E-commerce order document",
  "allOf": [
    { "$ref": "https://hai.ai/schemas/header/v1/header.schema.json" },
    {
      "type": "object",
      "properties": {
        "orderId": {
          "type": "string",
          "pattern": "^ORD-[0-9]{6}$"
        },
        "customer": {
          "type": "object",
          "properties": {
            "name": { "type": "string" },
            "email": { "type": "string", "format": "email" }
          },
          "required": ["name", "email"]
        },
        "items": {
          "type": "array",
          "minItems": 1,
          "items": {
            "type": "object",
            "properties": {
              "sku": { "type": "string" },
              "quantity": { "type": "integer", "minimum": 1 },
              "price": { "type": "number", "minimum": 0 }
            },
            "required": ["sku", "quantity", "price"]
          }
        },
        "total": {
          "type": "number",
          "minimum": 0
        },
        "status": {
          "type": "string",
          "enum": ["pending", "processing", "shipped", "delivered", "cancelled"]
        }
      },
      "required": ["orderId", "customer", "items", "total", "status"]
    }
  ]
}
  1. Use the schema when creating documents
import jacs
import json

agent = jacs.JacsAgent()
agent.load('./jacs.config.json')

order = agent.create_document(
    json.dumps({
        'orderId': 'ORD-123456',
        'customer': {
            'name': 'Jane Smith',
            'email': 'jane@example.com'
        },
        'items': [
            {'sku': 'WIDGET-001', 'quantity': 2, 'price': 29.99}
        ],
        'total': 59.98,
        'status': 'pending'
    }),
    custom_schema='./schemas/order.schema.json'
)
import { JacsAgent } from 'jacsnpm';

const agent = new JacsAgent();
agent.load('./jacs.config.json');

const order = agent.createDocument(
  JSON.stringify({
    orderId: 'ORD-123456',
    customer: {
      name: 'Jane Smith',
      email: 'jane@example.com'
    },
    items: [
      { sku: 'WIDGET-001', quantity: 2, price: 29.99 }
    ],
    total: 59.98,
    status: 'pending'
  }),
  './schemas/order.schema.json'
);

Schema Best Practices

Use Meaningful IDs

{
  "$id": "https://mycompany.com/schemas/v1/order.schema.json"
}

Include version in the path for schema evolution.

Document Everything

{
  "properties": {
    "status": {
      "type": "string",
      "description": "Current order status in the fulfillment workflow",
      "enum": ["pending", "processing", "shipped", "delivered", "cancelled"]
    }
  }
}

Use Appropriate Validation

{
  "properties": {
    "email": {
      "type": "string",
      "format": "email"
    },
    "phone": {
      "type": "string",
      "pattern": "^\\+?[1-9]\\d{1,14}$"
    },
    "quantity": {
      "type": "integer",
      "minimum": 1,
      "maximum": 1000
    }
  }
}
{
  "properties": {
    "shipping": {
      "type": "object",
      "properties": {
        "address": { "type": "string" },
        "city": { "type": "string" },
        "country": { "type": "string" },
        "postalCode": { "type": "string" }
      }
    },
    "billing": {
      "type": "object",
      "properties": {
        "address": { "type": "string" },
        "city": { "type": "string" },
        "country": { "type": "string" },
        "postalCode": { "type": "string" }
      }
    }
  }
}

Advanced Schema Features

Conditional Validation

Different requirements based on field values:

{
  "allOf": [
    { "$ref": "https://hai.ai/schemas/header/v1/header.schema.json" },
    {
      "type": "object",
      "properties": {
        "paymentMethod": {
          "type": "string",
          "enum": ["credit_card", "bank_transfer", "crypto"]
        }
      }
    }
  ],
  "if": {
    "properties": {
      "paymentMethod": { "const": "credit_card" }
    }
  },
  "then": {
    "properties": {
      "cardLastFour": {
        "type": "string",
        "pattern": "^[0-9]{4}$"
      }
    },
    "required": ["cardLastFour"]
  }
}

Reusable Definitions

{
  "$defs": {
    "address": {
      "type": "object",
      "properties": {
        "street": { "type": "string" },
        "city": { "type": "string" },
        "country": { "type": "string" },
        "postalCode": { "type": "string" }
      },
      "required": ["street", "city", "country"]
    }
  },
  "properties": {
    "shippingAddress": { "$ref": "#/$defs/address" },
    "billingAddress": { "$ref": "#/$defs/address" }
  }
}

Array Constraints

{
  "properties": {
    "tags": {
      "type": "array",
      "items": { "type": "string" },
      "minItems": 1,
      "maxItems": 10,
      "uniqueItems": true
    }
  }
}

Pattern Properties

For dynamic field names:

{
  "properties": {
    "metadata": {
      "type": "object",
      "patternProperties": {
        "^x-": { "type": "string" }
      },
      "additionalProperties": false
    }
  }
}

Schema Inheritance

Extending Custom Schemas

Create schema hierarchies:

// schemas/base-transaction.schema.json
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "https://example.com/schemas/base-transaction.schema.json",
  "allOf": [
    { "$ref": "https://hai.ai/schemas/header/v1/header.schema.json" },
    {
      "type": "object",
      "properties": {
        "transactionId": { "type": "string" },
        "timestamp": { "type": "string", "format": "date-time" },
        "amount": { "type": "number" }
      },
      "required": ["transactionId", "timestamp", "amount"]
    }
  ]
}
// schemas/payment.schema.json
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "https://example.com/schemas/payment.schema.json",
  "allOf": [
    { "$ref": "https://example.com/schemas/base-transaction.schema.json" },
    {
      "type": "object",
      "properties": {
        "paymentMethod": { "type": "string" },
        "processorId": { "type": "string" }
      },
      "required": ["paymentMethod"]
    }
  ]
}

Validation

Python Validation

import jacs
import json

agent = jacs.JacsAgent()
agent.load('./jacs.config.json')

try:
    # This will fail validation - missing required field
    doc = agent.create_document(
        json.dumps({
            'orderId': 'ORD-123456'
            # Missing: customer, items, total, status
        }),
        custom_schema='./schemas/order.schema.json'
    )
except Exception as e:
    print(f"Validation failed: {e}")

Node.js Validation

import { JacsAgent } from 'jacsnpm';

const agent = new JacsAgent();
agent.load('./jacs.config.json');

try {
  // This will fail - invalid enum value
  const doc = agent.createDocument(
    JSON.stringify({
      orderId: 'ORD-123456',
      customer: { name: 'Jane', email: 'jane@example.com' },
      items: [{ sku: 'A', quantity: 1, price: 10 }],
      total: 10,
      status: 'invalid_status'  // Not in enum
    }),
    './schemas/order.schema.json'
  );
} catch (error) {
  console.error('Validation failed:', error.message);
}

Example Schemas

Medical Record

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "https://healthcare.example.com/schemas/medical-record.schema.json",
  "title": "Medical Record",
  "allOf": [
    { "$ref": "https://hai.ai/schemas/header/v1/header.schema.json" },
    {
      "type": "object",
      "properties": {
        "patientId": { "type": "string" },
        "recordType": {
          "type": "string",
          "enum": ["visit", "lab_result", "prescription", "diagnosis"]
        },
        "provider": {
          "type": "object",
          "properties": {
            "name": { "type": "string" },
            "npi": { "type": "string", "pattern": "^[0-9]{10}$" }
          }
        },
        "date": { "type": "string", "format": "date" },
        "notes": { "type": "string" },
        "confidential": { "type": "boolean", "default": true }
      },
      "required": ["patientId", "recordType", "provider", "date"]
    }
  ]
}
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "https://legal.example.com/schemas/contract.schema.json",
  "title": "Legal Contract",
  "allOf": [
    { "$ref": "https://hai.ai/schemas/header/v1/header.schema.json" },
    {
      "type": "object",
      "properties": {
        "contractNumber": { "type": "string" },
        "parties": {
          "type": "array",
          "minItems": 2,
          "items": {
            "type": "object",
            "properties": {
              "name": { "type": "string" },
              "role": { "type": "string" },
              "agentId": { "type": "string", "format": "uuid" }
            }
          }
        },
        "effectiveDate": { "type": "string", "format": "date" },
        "expirationDate": { "type": "string", "format": "date" },
        "terms": { "type": "string" },
        "jurisdiction": { "type": "string" }
      },
      "required": ["contractNumber", "parties", "effectiveDate", "terms"]
    }
  ]
}

IoT Sensor Reading

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "https://iot.example.com/schemas/sensor-reading.schema.json",
  "title": "Sensor Reading",
  "allOf": [
    { "$ref": "https://hai.ai/schemas/header/v1/header.schema.json" },
    {
      "type": "object",
      "properties": {
        "deviceId": { "type": "string" },
        "sensorType": {
          "type": "string",
          "enum": ["temperature", "humidity", "pressure", "motion"]
        },
        "value": { "type": "number" },
        "unit": { "type": "string" },
        "timestamp": { "type": "string", "format": "date-time" },
        "location": {
          "type": "object",
          "properties": {
            "latitude": { "type": "number" },
            "longitude": { "type": "number" }
          }
        }
      },
      "required": ["deviceId", "sensorType", "value", "timestamp"]
    }
  ]
}

Schema Versioning

Version in Path

{
  "$id": "https://example.com/schemas/v1/order.schema.json"
}

Version Field

{
  "properties": {
    "schemaVersion": {
      "type": "string",
      "const": "1.0.0"
    }
  }
}

Migration Strategy

  1. Create new schema version
  2. Update application to support both versions
  3. Migrate existing documents
  4. Deprecate old version

See Also

Testing

This chapter covers testing strategies for applications that use JACS, including unit testing, integration testing, and mocking approaches.

Testing Fundamentals

Test Agent Setup

Create dedicated test configurations to isolate tests from production:

// jacs.test.config.json
{
  "$schema": "https://hai.ai/schemas/jacs.config.schema.json",
  "jacs_data_directory": "./test_data",
  "jacs_key_directory": "./test_keys",
  "jacs_agent_private_key_filename": "test_private.pem",
  "jacs_agent_public_key_filename": "test_public.pem",
  "jacs_agent_key_algorithm": "ring-Ed25519",
  "jacs_default_storage": "fs"
}

Test Fixtures

Set up test fixtures before running tests:

Python (pytest):

import pytest
import jacs
import tempfile
import shutil

@pytest.fixture
def test_agent():
    """Create a test agent with temporary directories."""
    temp_dir = tempfile.mkdtemp()
    data_dir = f"{temp_dir}/data"
    key_dir = f"{temp_dir}/keys"

    # Initialize directories
    import os
    os.makedirs(data_dir)
    os.makedirs(key_dir)

    # Create test config
    config = {
        "jacs_data_directory": data_dir,
        "jacs_key_directory": key_dir,
        "jacs_agent_key_algorithm": "ring-Ed25519",
        "jacs_default_storage": "fs"
    }

    config_path = f"{temp_dir}/jacs.config.json"
    with open(config_path, 'w') as f:
        import json
        json.dump(config, f)

    agent = jacs.JacsAgent()
    agent.load(config_path)

    yield agent

    # Cleanup
    shutil.rmtree(temp_dir)

def test_create_document(test_agent):
    """Test document creation."""
    import json
    doc = test_agent.create_document(json.dumps({
        'title': 'Test Document'
    }))

    assert doc is not None
    parsed = json.loads(doc)
    assert 'jacsId' in parsed
    assert 'jacsSignature' in parsed

Node.js (Jest):

import { JacsAgent } from 'jacsnpm';
import fs from 'fs';
import path from 'path';
import os from 'os';

describe('JACS Document Tests', () => {
  let agent;
  let tempDir;

  beforeAll(() => {
    // Create temp directory
    tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'jacs-test-'));
    const dataDir = path.join(tempDir, 'data');
    const keyDir = path.join(tempDir, 'keys');

    fs.mkdirSync(dataDir);
    fs.mkdirSync(keyDir);

    // Create test config
    const config = {
      jacs_data_directory: dataDir,
      jacs_key_directory: keyDir,
      jacs_agent_key_algorithm: 'ring-Ed25519',
      jacs_default_storage: 'fs'
    };

    const configPath = path.join(tempDir, 'jacs.config.json');
    fs.writeFileSync(configPath, JSON.stringify(config));

    agent = new JacsAgent();
    agent.load(configPath);
  });

  afterAll(() => {
    // Cleanup
    fs.rmSync(tempDir, { recursive: true });
  });

  test('creates a signed document', () => {
    const doc = agent.createDocument(JSON.stringify({
      title: 'Test Document'
    }));

    const parsed = JSON.parse(doc);
    expect(parsed.jacsId).toBeDefined();
    expect(parsed.jacsSignature).toBeDefined();
  });
});

Unit Testing

Testing Document Operations

import pytest
import jacs
import json

def test_document_verification(test_agent):
    """Test that created documents verify correctly."""
    doc = test_agent.create_document(json.dumps({
        'content': 'Test content'
    }))

    is_valid = test_agent.verify_document(doc)
    assert is_valid is True

def test_document_tampering_detected(test_agent):
    """Test that tampered documents fail verification."""
    doc = test_agent.create_document(json.dumps({
        'content': 'Original content'
    }))

    # Tamper with the document
    parsed = json.loads(doc)
    parsed['content'] = 'Tampered content'
    tampered = json.dumps(parsed)

    is_valid = test_agent.verify_document(tampered)
    assert is_valid is False

def test_signature_verification(test_agent):
    """Test signature verification."""
    doc = test_agent.create_document(json.dumps({
        'data': 'test'
    }))

    is_valid = test_agent.verify_signature(doc)
    assert is_valid is True

Testing Agreements

def test_agreement_creation(test_agent):
    """Test creating a document with agreement."""
    doc = test_agent.create_document(json.dumps({
        'contract': 'Service Agreement'
    }))

    # Add agreement
    doc_with_agreement = test_agent.create_agreement(
        doc,
        ['agent-1-id', 'agent-2-id'],
        question='Do you agree to these terms?',
        context='Test agreement'
    )

    parsed = json.loads(doc_with_agreement)
    assert 'jacsAgreement' in parsed
    assert len(parsed['jacsAgreement']['agentIDs']) == 2

def test_agreement_signing(test_agent):
    """Test signing an agreement."""
    doc = test_agent.create_document(json.dumps({
        'contract': 'Test'
    }))

    agent_json = test_agent.load('./jacs.test.config.json')
    agent_data = json.loads(agent_json)
    agent_id = agent_data['jacsId']

    doc_with_agreement = test_agent.create_agreement(
        doc,
        [agent_id],
        question='Agree?'
    )

    signed = test_agent.sign_agreement(doc_with_agreement)

    status_json = test_agent.check_agreement(signed)
    status = json.loads(status_json)

    assert status['complete'] is True

Testing Request/Response Signing

def test_request_signing(test_agent):
    """Test signing a request payload."""
    payload = {
        'method': 'tools/call',
        'params': {'name': 'test_tool'}
    }

    signed = test_agent.sign_request(payload)
    assert signed is not None

    # Verify the signed request
    result = test_agent.verify_response(signed)
    assert result is not None
    assert 'payload' in result

Integration Testing

Testing MCP Integration

Python:

import pytest
import asyncio
from jacs.mcp import JACSMCPServer, JACSMCPClient
from fastmcp import FastMCP

@pytest.fixture
def mcp_server(test_agent):
    """Create a test MCP server."""
    mcp = FastMCP("Test Server")

    @mcp.tool()
    def echo(text: str) -> str:
        return f"Echo: {text}"

    return JACSMCPServer(mcp)

@pytest.mark.asyncio
async def test_mcp_tool_call(mcp_server, test_agent):
    """Test calling an MCP tool with JACS authentication."""
    # This would require setting up actual server/client connection
    # For unit testing, test the signing/verification separately
    pass

Node.js:

import { JACSExpressMiddleware } from 'jacsnpm/http';
import express from 'express';
import request from 'supertest';

describe('JACS Express Middleware', () => {
  let app;
  let agent;

  beforeAll(() => {
    app = express();
    app.use('/api', express.text({ type: '*/*' }));
    app.use('/api', JACSExpressMiddleware({
      configPath: './jacs.test.config.json'
    }));

    app.post('/api/echo', (req, res) => {
      res.send({ echo: req.jacsPayload });
    });

    agent = new JacsAgent();
    agent.load('./jacs.test.config.json');
  });

  test('accepts valid JACS requests', async () => {
    const signedRequest = agent.signRequest({
      message: 'Hello'
    });

    const response = await request(app)
      .post('/api/echo')
      .set('Content-Type', 'text/plain')
      .send(signedRequest);

    expect(response.status).toBe(200);
  });

  test('rejects invalid requests', async () => {
    const response = await request(app)
      .post('/api/echo')
      .set('Content-Type', 'text/plain')
      .send('{"invalid": "request"}');

    expect(response.status).toBe(400);
  });
});

Testing HTTP Endpoints

import pytest
from fastapi import FastAPI
from fastapi.testclient import TestClient
import jacs
import json

app = FastAPI()

@app.post("/api/document")
async def create_doc(request_body: str):
    agent = jacs.JacsAgent()
    agent.load('./jacs.test.config.json')

    result = agent.verify_response(request_body)
    if result:
        # Process the verified payload
        return {"status": "success", "payload": result.get("payload")}
    return {"status": "error"}

@pytest.fixture
def client():
    return TestClient(app)

def test_endpoint_accepts_signed_request(client, test_agent):
    """Test that endpoint accepts properly signed requests."""
    signed = test_agent.sign_request({
        'action': 'create',
        'data': {'title': 'Test'}
    })

    response = client.post("/api/document", content=signed)
    assert response.status_code == 200

Mocking

Mocking JACS Agent

Python:

from unittest.mock import Mock, patch
import json

def test_with_mocked_agent():
    """Test with a mocked JACS agent."""
    mock_agent = Mock()

    # Mock create_document to return a fake signed document
    mock_agent.create_document.return_value = json.dumps({
        'jacsId': 'mock-id',
        'jacsVersion': 'mock-version',
        'content': 'test',
        'jacsSignature': {'signature': 'mock-sig'}
    })

    # Mock verify_document to always return True
    mock_agent.verify_document.return_value = True

    # Use the mock in your tests
    doc = mock_agent.create_document(json.dumps({'content': 'test'}))
    assert mock_agent.verify_document(doc) is True

Node.js:

// Mock for testing
const mockAgent = {
  createDocument: jest.fn().mockReturnValue(JSON.stringify({
    jacsId: 'mock-id',
    jacsVersion: 'mock-version',
    content: 'test',
    jacsSignature: { signature: 'mock-sig' }
  })),
  verifyDocument: jest.fn().mockReturnValue(true),
  signRequest: jest.fn().mockImplementation((payload) =>
    JSON.stringify({ payload, jacsSignature: { signature: 'mock' } })
  ),
  verifyResponse: jest.fn().mockImplementation((response) =>
    ({ payload: JSON.parse(response).payload })
  )
};

test('uses mocked agent', () => {
  const doc = mockAgent.createDocument(JSON.stringify({ test: true }));
  expect(mockAgent.createDocument).toHaveBeenCalled();
  expect(mockAgent.verifyDocument(doc)).toBe(true);
});

Mocking MCP Transport

// Mock transport for MCP testing
class MockTransport {
  constructor() {
    this.messages = [];
  }

  send(message) {
    this.messages.push(message);
  }

  async receive() {
    return this.messages.shift();
  }
}

test('MCP client with mock transport', async () => {
  const mockTransport = new MockTransport();
  // Use mock transport in tests
});

Test Coverage

Python Coverage

# Run tests with coverage
pytest --cov=myapp --cov-report=html tests/

# View coverage report
open htmlcov/index.html

Node.js Coverage

# Run tests with coverage
npm test -- --coverage

# Or with Jest directly
jest --coverage

CI/CD Integration

GitHub Actions

# .github/workflows/test.yml
name: Tests

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v3

      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '18'

      - name: Install dependencies
        run: npm install

      - name: Generate test keys
        run: |
          mkdir -p test_keys test_data
          # Generate test keys (implementation depends on your setup)

      - name: Run tests
        run: npm test

      - name: Upload coverage
        uses: codecov/codecov-action@v3

Test Environment Variables

# Set test environment
export JACS_TEST_MODE=1
export JACS_TEST_CONFIG=./jacs.test.config.json

Best Practices

1. Isolate Tests

  • Use separate test configurations
  • Create temporary directories for each test run
  • Clean up after tests

2. Test Edge Cases

def test_empty_document():
    """Test handling of empty documents."""
    with pytest.raises(Exception):
        test_agent.create_document('')

def test_invalid_json():
    """Test handling of invalid JSON."""
    with pytest.raises(Exception):
        test_agent.create_document('not json')

def test_large_document():
    """Test handling of large documents."""
    large_content = 'x' * 1000000
    doc = test_agent.create_document(json.dumps({
        'content': large_content
    }))
    assert doc is not None

3. Test Security Properties

def test_signature_changes_with_content():
    """Verify different content produces different signatures."""
    doc1 = test_agent.create_document(json.dumps({'a': 1}))
    doc2 = test_agent.create_document(json.dumps({'a': 2}))

    sig1 = json.loads(doc1)['jacsSignature']['signature']
    sig2 = json.loads(doc2)['jacsSignature']['signature']

    assert sig1 != sig2

4. Test Error Handling

def test_verify_invalid_signature():
    """Test that invalid signatures are rejected."""
    doc = test_agent.create_document(json.dumps({'data': 'test'}))
    parsed = json.loads(doc)

    # Corrupt the signature
    parsed['jacsSignature']['signature'] = 'invalid'
    corrupted = json.dumps(parsed)

    assert test_agent.verify_document(corrupted) is False

See Also

Model Context Protocol (MCP)

JACS provides comprehensive integration with the Model Context Protocol (MCP), enabling cryptographically signed and verified communication between AI agents and MCP servers.

What is MCP?

Model Context Protocol is an open standard created by Anthropic for AI models to securely access external tools, data, and services. MCP defines:

  • Tools: Functions that AI models can call
  • Resources: Data sources that models can read
  • Prompts: Pre-defined prompt templates
  • Transports: Communication channels (STDIO, SSE, WebSocket)

Why JACS + MCP?

JACS enhances MCP by adding a security layer that standard MCP lacks:

FeatureStandard MCPJACS MCP
Message SigningNoYes
Identity VerificationNoYes
Tamper DetectionNoYes
Audit TrailNoYes
Non-RepudiationNoYes

This makes JACS MCP suitable for:

  • Multi-agent systems requiring trust
  • Financial and legal AI applications
  • Healthcare AI systems
  • Enterprise deployments
  • Any scenario where message authenticity matters

Architecture

JACS uses a transport proxy pattern that wraps any MCP transport with cryptographic signing and verification:

┌─────────────────────────────────────────────────────────────┐
│                      MCP Application                         │
├─────────────────────────────────────────────────────────────┤
│                       MCP SDK                                │
├─────────────────────────────────────────────────────────────┤
│                  JACS Transport Proxy                        │
│  ┌─────────────┐                    ┌──────────────┐        │
│  │ Outgoing:   │                    │ Incoming:    │        │
│  │ signRequest │                    │ verifyResp   │        │
│  └─────────────┘                    └──────────────┘        │
├─────────────────────────────────────────────────────────────┤
│               Underlying Transport                           │
│           (STDIO / SSE / WebSocket)                         │
└─────────────────────────────────────────────────────────────┘

How It Works

  1. Outgoing Messages: The proxy intercepts JSON-RPC messages and signs them with the agent's private key
  2. Incoming Messages: The proxy verifies signatures before passing messages to the application
  3. Graceful Fallback: If verification fails, messages can be passed through as plain JSON for interoperability

Quick Start

Node.js

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { createJACSTransportProxy } from 'jacsnpm/mcp';
import { z } from 'zod';

// Create transport with JACS encryption
const baseTransport = new StdioServerTransport();
const secureTransport = createJACSTransportProxy(
  baseTransport,
  "./jacs.config.json",
  "server"
);

// Create MCP server
const server = new McpServer({
  name: "my-secure-server",
  version: "1.0.0"
});

// Register tools (standard MCP API)
server.tool("add", {
  a: z.number(),
  b: z.number()
}, async ({ a, b }) => {
  return { content: [{ type: "text", text: `${a + b}` }] };
});

// Connect with JACS encryption
await server.connect(secureTransport);

Python

import jacs
from jacs.mcp import JACSMCPServer
from fastmcp import FastMCP
import uvicorn

# Initialize JACS agent
agent = jacs.JacsAgent()
agent.load("./jacs.config.json")

# Create FastMCP server with JACS authentication
mcp = JACSMCPServer(FastMCP("Secure Server"))

@mcp.tool()
def add(a: int, b: int) -> str:
    """Add two numbers"""
    return str(a + b)

# Get ASGI app with JACS middleware
app = mcp.sse_app()

if __name__ == "__main__":
    uvicorn.run(app, host="localhost", port=8000)

Language Support

JACS provides native MCP integration for both major platforms:

Node.js (jacsnpm)

The Node.js integration uses a transport proxy pattern that works with any MCP transport:

  • STDIO: For CLI tools and subprocess communication
  • SSE: For web-based servers
  • WebSocket: For bidirectional streaming

Key classes:

  • JACSTransportProxy - Wraps any transport with signing/verification
  • createJACSTransportProxy() - Factory function

See Node.js MCP Integration for complete documentation.

Python (jacspy)

The Python integration uses middleware wrappers for FastMCP:

  • JACSMCPServer - Wraps FastMCP servers with authentication
  • JACSMCPClient - Wraps FastMCP clients with signing

Key classes:

  • JACSMCPServer - Server wrapper with JACS middleware
  • JACSMCPClient - Client wrapper with interceptors

See Python MCP Integration for complete documentation.

Message Flow

Tool Call Example

When a client calls a tool on a JACS-enabled MCP server:

Client                          Server
  │                               │
  │  1. Create JSON-RPC request   │
  │  2. Sign with signRequest()   │
  │  ──────────────────────────>  │
  │                               │ 3. Verify with verifyRequest()
  │                               │ 4. Execute tool
  │                               │ 5. Sign response with signResponse()
  │  <──────────────────────────  │
  │  6. Verify with verifyResponse() │
  │  7. Extract payload           │

Signed Message Structure

A JACS-signed MCP message contains:

{
  "jacsId": "unique-document-id",
  "jacsVersion": "version-uuid",
  "jacsSignature": {
    "agentID": "signing-agent-id",
    "agentVersion": "agent-version",
    "date": "2024-01-15T10:30:00Z",
    "signature": "base64-signature",
    "signingAlgorithm": "ring-Ed25519"
  },
  "jacsSha256": "content-hash",
  "payload": {
    "jsonrpc": "2.0",
    "method": "tools/call",
    "params": { "name": "add", "arguments": { "a": 5, "b": 3 } },
    "id": 1
  }
}

Configuration

Server Configuration

{
  "$schema": "https://hai.ai/schemas/jacs.config.schema.json",
  "jacs_data_directory": "./jacs_data",
  "jacs_key_directory": "./jacs_keys",
  "jacs_agent_key_algorithm": "ring-Ed25519",
  "jacs_agent_id_and_version": "server-agent-id:version",
  "jacs_default_storage": "fs"
}

Client Configuration

Each MCP client needs its own JACS agent identity:

{
  "$schema": "https://hai.ai/schemas/jacs.config.schema.json",
  "jacs_data_directory": "./jacs_data",
  "jacs_key_directory": "./jacs_keys",
  "jacs_agent_key_algorithm": "ring-Ed25519",
  "jacs_agent_id_and_version": "client-agent-id:version",
  "jacs_default_storage": "fs"
}

Transports

STDIO

Best for CLI tools and subprocess communication:

// Node.js
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";

const baseTransport = new StdioServerTransport();
const secureTransport = createJACSTransportProxy(
  baseTransport,
  "./jacs.config.json",
  "server"
);

Important: Debug logging goes to stderr to keep stdout clean for JSON-RPC.

Server-Sent Events (SSE)

For web-based MCP servers:

# Python with FastMCP
from jacs.mcp import JACSMCPServer
from fastmcp import FastMCP

mcp = JACSMCPServer(FastMCP("Web Server"))
app = mcp.sse_app()  # Returns ASGI app with JACS middleware
// Node.js with Express
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
import express from 'express';

const app = express();
app.get('/sse', (req, res) => {
  const baseTransport = new SSEServerTransport('/messages', res);
  const secureTransport = createJACSTransportProxy(
    baseTransport,
    "./jacs.config.json",
    "server"
  );
  server.connect(secureTransport);
});

Security Model

What Gets Signed

  • All JSON-RPC requests and responses
  • Tool calls and results
  • Resource requests and data
  • Prompt requests and templates

What Gets Verified

  • Agent identity (agentID)
  • Message integrity (jacsSha256)
  • Signature validity (jacsSignature)
  • Optional: DNS-based identity verification

Passthrough Mode

For interoperability with non-JACS MCP systems, the proxy can fall back to plain JSON:

  1. Try to verify as JACS artifact
  2. If verification fails, parse as plain JSON
  3. Pass clean message to application

To enforce JACS-only communication, implement custom validation in your tools.

Debugging

Enable Debug Logging

# Node.js
export JACS_MCP_DEBUG=true

# Python
import logging
logging.basicConfig(level=logging.DEBUG)

Common Issues

IssueCauseSolution
"JACS not operational"Config path incorrectVerify config file path
Verification failuresIncompatible keysEnsure matching key algorithms
Empty responsesNull value handlingCheck message serialization
Connection timeoutsNetwork issuesVerify server is running

Best Practices

1. Separate Keys for Server and Client

project/
├── server/
│   ├── jacs.config.json
│   └── jacs_keys/
│       ├── private.pem
│       └── public.pem
└── client/
    ├── jacs.config.json
    └── jacs_keys/
        ├── private.pem
        └── public.pem

2. Use TLS for Network Transports

# Use HTTPS for SSE
client = JACSMCPClient("https://server.example.com/sse")

3. Implement Key Rotation

Update agent versions when rotating keys:

{
  "jacs_agent_id_and_version": "my-agent:v2"
}

4. Log Security Events

# Production logging setup
import logging

logging.getLogger("jacs").setLevel(logging.INFO)
logging.getLogger("jacs.security").setLevel(logging.WARNING)

Example: Multi-Agent System

A complete example with multiple JACS-authenticated agents:

┌──────────────────┐     ┌──────────────────┐     ┌──────────────────┐
│   Agent A        │     │   MCP Server     │     │   Agent B        │
│  (Data Analyst)  │────>│  (Tool Provider) │<────│  (Report Writer) │
│                  │     │                  │     │                  │
│ Signs requests   │     │ Verifies both    │     │ Signs requests   │
│ Verifies resps   │     │ Signs responses  │     │ Verifies resps   │
└──────────────────┘     └──────────────────┘     └──────────────────┘

Each agent has its own:

  • JACS agent ID and version
  • Private/public key pair
  • Configuration file

The MCP server verifies requests from both agents and signs all responses.

See Also

OpenClaw Integration

OpenClaw is a personal AI assistant platform. This chapter covers integrating JACS with OpenClaw to enable cryptographically signed agent-to-agent communication.

Overview

The JACS OpenClaw plugin enables:

  • Bootstrap JACS identity via openclaw plugin setup jacs
  • Securely store cryptographic keys using OpenClaw's configuration system
  • Sign and verify all agent communications with post-quantum cryptographic provenance
  • Publish agent identity via .well-known endpoints
  • P2P agent verification without requiring a central registry

Installation

# Install the JACS plugin for OpenClaw
openclaw plugins install @openclaw/jacs

Setup

Initialize JACS Identity

# Interactive setup wizard
openclaw jacs init

This will:

  1. Select a key algorithm (pq2025/dilithium/rsa/ecdsa)
  2. Generate a cryptographic key pair
  3. Create an agent identity
  4. Store keys in ~/.openclaw/jacs_keys/

Configuration

The plugin configuration is stored in ~/.openclaw/openclaw.json:

{
  "plugins": {
    "entries": {
      "jacs": {
        "enabled": true,
        "config": {
          "keyAlgorithm": "pq2025",
          "autoSign": false,
          "autoVerify": false,
          "agentId": "89fb9d88-6990-420f-8df9-252ccdfdfd3d",
          "agentName": "My OpenClaw Agent",
          "agentDescription": "Personal AI assistant with JACS identity"
        }
      }
    }
  }
}

Configuration Options

OptionTypeDefaultDescription
keyAlgorithmstringpq2025Signing algorithm (pq2025, pq-dilithium, rsa, ecdsa)
autoSignbooleanfalseAutomatically sign outbound messages
autoVerifybooleanfalseAutomatically verify inbound JACS-signed messages
agentNamestring-Human-readable agent name
agentDescriptionstring-Agent description for A2A discovery
agentDomainstring-Domain for agent identity (DNSSEC validated)

Directory Structure

~/.openclaw/
├── openclaw.json              # Plugin config (non-sensitive)
├── jacs/                      # JACS data directory
│   ├── jacs.config.json       # JACS configuration
│   └── agent/                 # Agent documents
└── jacs_keys/                 # Key directory (encrypted)
    ├── agent.private.pem.enc  # AES-256-GCM encrypted
    └── agent.public.pem       # Public key (shareable)

CLI Commands

Status

openclaw jacs status

Shows JACS status, agent ID, algorithm, and registration state.

Sign Document

openclaw jacs sign <file>

Sign a JSON document with your JACS identity.

Verify Document

openclaw jacs verify <file>

Verify a JACS-signed document.

Lookup Agent

openclaw jacs lookup <domain>

Look up another agent's public key from their domain.

Export Agent Card

openclaw jacs export-card

Export your agent as an A2A Agent Card.

Generate DNS Record

openclaw jacs dns-record <domain>

Generate DNS TXT record commands for publishing your agent fingerprint.

Agent Tools

The plugin provides tools for use in agent conversations:

jacs_sign

Sign a document with JACS cryptographic provenance.

{
  "name": "jacs_sign",
  "parameters": {
    "document": { "message": "hello" },
    "artifactType": "message"
  }
}

jacs_verify

Verify a JACS-signed document.

{
  "name": "jacs_verify",
  "parameters": {
    "document": { "...signed document..." }
  }
}

jacs_fetch_pubkey

Fetch another agent's public key from their domain.

{
  "name": "jacs_fetch_pubkey",
  "parameters": {
    "domain": "other-agent.example.com"
  }
}

jacs_verify_with_key

Verify a document using a fetched public key.

{
  "name": "jacs_verify_with_key",
  "parameters": {
    "document": { "...signed document..." },
    "publicKey": "-----BEGIN PUBLIC KEY-----..."
  }
}

jacs_lookup_agent

Lookup a JACS agent by domain or ID.

{
  "name": "jacs_lookup_agent",
  "parameters": {
    "domain": "agent.example.com"
  }
}

jacs_create_agreement

Create a multi-party agreement requiring signatures from multiple agents.

{
  "name": "jacs_create_agreement",
  "parameters": {
    "document": { "terms": "..." },
    "agentIds": ["agent-1-uuid", "agent-2-uuid"],
    "question": "Do you agree to these terms?"
  }
}

Well-Known Endpoints

When OpenClaw's gateway is running, the plugin serves:

/.well-known/agent-card.json

A2A v0.4.0 Agent Card with JACS extension.

/.well-known/jacs-pubkey.json

Your agent's public key for verification:

{
  "publicKey": "-----BEGIN PUBLIC KEY-----...",
  "publicKeyHash": "sha256-hash",
  "algorithm": "pq2025",
  "agentId": "agent-uuid",
  "timestamp": "2024-01-15T10:30:00Z"
}

P2P Agent Verification

Agents can verify each other without a central registry:

Flow

  1. Agent A publishes their public key at /.well-known/jacs-pubkey.json
  2. Agent A optionally sets DNS TXT record at _v1.agent.jacs.<domain>.
  3. Agent A signs a document with jacs_sign
  4. Agent B receives the signed document
  5. Agent B fetches Agent A's key with jacs_fetch_pubkey
  6. Agent B verifies with jacs_verify_with_key

Example Workflow

Agent A (agent-a.example.com)          Agent B (agent-b.example.com)
================================       ================================

1. Initialize JACS
   openclaw jacs init

2. Publish public key
   (served at /.well-known/jacs-pubkey.json)

3. Sign a message
   jacs_sign({ message: "hello" })
   → signed_doc

4. Send signed_doc to Agent B
                                       5. Receive signed_doc

                                       6. Fetch Agent A's public key
                                          jacs_fetch_pubkey("agent-a.example.com")
                                          → pubkey_a

                                       7. Verify signature
                                          jacs_verify_with_key(signed_doc, pubkey_a)
                                          → { valid: true, signer: "agent-a-uuid" }

DNS-Based Discovery

For additional verification, agents can publish their public key fingerprint in DNS:

Generate DNS Record

openclaw jacs dns-record agent.example.com

This outputs commands for your DNS provider:

_v1.agent.jacs.agent.example.com. 3600 IN TXT "v=hai.ai; jacs_agent_id=<UUID>; alg=SHA-256; enc=base64; jac_public_key_hash=<44-char-b64>"

DNS Lookup

openclaw jacs lookup agent.example.com

The plugin will:

  1. Query DNS TXT record at _v1.agent.jacs.agent.example.com
  2. Fetch full public key from /.well-known/jacs-pubkey.json
  3. Verify the DNS hash matches the fetched key

Security

Key Protection

  • Private keys are encrypted with AES-256-GCM
  • Password-derived key using PBKDF2 (100k iterations)
  • Keys stored with restricted file permissions

Post-Quantum Cryptography

The default algorithm (pq2025 / ML-DSA-87) is quantum-resistant, providing protection against future quantum computing attacks.

Signature Binding

Signatures include:

  • Document hash (prevents modification)
  • Signer's agent ID and version
  • Timestamp
  • List of signed fields

Skill Usage

The plugin provides a skill for agent conversations:

/jacs sign {"task": "analyze data", "result": "completed"}
/jacs verify <paste signed document>
/jacs lookup agent.example.com

Troubleshooting

"JACS not initialized"

Run openclaw jacs init to set up your JACS identity.

"Failed to fetch public key"

Verify the domain is correct and serving /.well-known/jacs-pubkey.json.

"Signature verification failed"

  • Check that the document hasn't been modified
  • Verify you have the correct public key for the signer
  • Ensure the signing algorithm matches

Next Steps

Web Servers

JACS provides middleware and utilities for building HTTP servers with cryptographic request/response signing across multiple frameworks and languages.

Overview

Web server integration with JACS enables:

  • Request Authentication: Verify incoming requests were signed by valid agents
  • Response Signing: Automatically sign outgoing responses
  • Tamper Detection: Ensure message integrity end-to-end
  • Audit Trail: Track all authenticated interactions

Supported Frameworks

FrameworkLanguageModule
Express.jsNode.jsjacsnpm/http
KoaNode.jsjacsnpm/http
FastAPIPythonjacs.http
FlaskPythonjacs.http

Request/Response Flow

All JACS web integrations follow the same flow:

Client                          Server
  │                               │
  │── signRequest(payload) ────>  │
  │                               │── verifyRequest()
  │                               │── process request
  │                               │── signResponse(result)
  │<── verifyResponse(result) ──  │
  │

Node.js Integration

Express.js

import express from 'express';
import { JACSExpressMiddleware } from 'jacsnpm/http';

const app = express();

// IMPORTANT: Parse body as text BEFORE JACS middleware
app.use('/api', express.text({ type: '*/*' }));

// Apply JACS middleware
app.use('/api', JACSExpressMiddleware({
  configPath: './jacs.config.json'
}));

// Route handlers receive verified payload in req.jacsPayload
app.post('/api/data', (req, res) => {
  const payload = req.jacsPayload;

  if (!payload) {
    return res.status(400).send({ error: 'Invalid JACS request' });
  }

  // Response objects are automatically signed
  res.send({
    received: payload,
    status: 'ok'
  });
});

app.listen(3000);

Koa

import Koa from 'koa';
import { JACSKoaMiddleware } from 'jacsnpm/http';

const app = new Koa();

// Apply JACS middleware (handles body parsing internally)
app.use(JACSKoaMiddleware({
  configPath: './jacs.config.json'
}));

app.use(async (ctx) => {
  if (ctx.path === '/api/data' && ctx.method === 'POST') {
    const payload = ctx.state.jacsPayload;

    if (!payload) {
      ctx.status = 400;
      ctx.body = { error: 'Invalid JACS request' };
      return;
    }

    // Response objects are automatically signed
    ctx.body = {
      received: payload,
      status: 'ok'
    };
  }
});

app.listen(3000);

See Node.js HTTP Server and Express Middleware for complete documentation.

Python Integration

FastAPI

from fastapi import FastAPI, Request
from fastapi.responses import PlainTextResponse
import jacs
import json

app = FastAPI()

# Initialize JACS agent
agent = jacs.JacsAgent()
agent.load("./jacs.config.json")

@app.post("/api/data")
async def handle_data(request: Request):
    # Read raw body
    body = await request.body()
    body_str = body.decode('utf-8')

    # Verify JACS request
    try:
        verified = jacs.verify_request(body_str)
        payload = json.loads(verified).get('payload')
    except Exception as e:
        return PlainTextResponse(
            content=json.dumps({"error": "Invalid JACS request"}),
            status_code=400
        )

    # Process request
    result = {"received": payload, "status": "ok"}

    # Sign response
    signed_response = jacs.sign_response(result)

    return PlainTextResponse(content=signed_response)

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="localhost", port=8000)

Flask

from flask import Flask, request
import jacs
import json

app = Flask(__name__)

# Initialize JACS agent
agent = jacs.JacsAgent()
agent.load("./jacs.config.json")

@app.route("/api/data", methods=["POST"])
def handle_data():
    # Read raw body
    body_str = request.get_data(as_text=True)

    # Verify JACS request
    try:
        verified = jacs.verify_request(body_str)
        payload = json.loads(verified).get('payload')
    except Exception as e:
        return json.dumps({"error": "Invalid JACS request"}), 400

    # Process request
    result = {"received": payload, "status": "ok"}

    # Sign response
    signed_response = jacs.sign_response(result)

    return signed_response, 200, {"Content-Type": "text/plain"}

if __name__ == "__main__":
    app.run(port=8000)

HTTP Client

Node.js Client

import jacs from 'jacsnpm';

async function sendJacsRequest(url, payload) {
  // Load JACS agent
  await jacs.load('./jacs.client.config.json');

  // Sign the request
  const signedRequest = await jacs.signRequest(payload);

  // Send HTTP request
  const response = await fetch(url, {
    method: 'POST',
    headers: { 'Content-Type': 'text/plain' },
    body: signedRequest
  });

  // Verify response
  const responseText = await response.text();
  const verified = await jacs.verifyResponse(responseText);

  return verified.payload;
}

// Usage
const result = await sendJacsRequest('http://localhost:3000/api/data', {
  action: 'fetch',
  query: { id: 42 }
});

Python Client

import jacs
import requests
import json

def send_jacs_request(url, payload):
    # Initialize JACS agent
    agent = jacs.JacsAgent()
    agent.load("./jacs.client.config.json")

    # Sign the request
    signed_request = jacs.sign_request(payload)

    # Send HTTP request
    response = requests.post(
        url,
        data=signed_request,
        headers={"Content-Type": "text/plain"}
    )

    # Verify response
    verified = jacs.verify_response(response.text)
    return json.loads(verified).get('payload')

# Usage
result = send_jacs_request("http://localhost:8000/api/data", {
    "action": "fetch",
    "query": {"id": 42}
})

Middleware Patterns

Route-Level Protection

Protect specific routes while leaving others public:

// Node.js Express
const app = express();

// Public routes (no JACS)
app.get('/health', (req, res) => res.send({ status: 'ok' }));
app.get('/public/info', (req, res) => res.send({ name: 'My API' }));

// Protected routes (JACS required)
app.use('/api', express.text({ type: '*/*' }));
app.use('/api', JACSExpressMiddleware({ configPath: './jacs.config.json' }));

app.post('/api/secure', (req, res) => {
  // Only JACS-signed requests reach here
  res.send({ data: 'secure response' });
});

Multiple Agent Configurations

Use different JACS agents for different route groups:

// Admin routes with admin agent
app.use('/admin', express.text({ type: '*/*' }));
app.use('/admin', JACSExpressMiddleware({
  configPath: './jacs.admin.config.json'
}));

// User routes with user agent
app.use('/user', express.text({ type: '*/*' }));
app.use('/user', JACSExpressMiddleware({
  configPath: './jacs.user.config.json'
}));

Validation Middleware

Create reusable validation helpers:

function requireJacsPayload(req, res, next) {
  if (!req.jacsPayload) {
    return res.status(400).json({
      error: 'JACS verification failed',
      message: 'Request must be signed with valid JACS credentials'
    });
  }
  next();
}

// Apply to routes
app.post('/api/secure', requireJacsPayload, (req, res) => {
  // Guaranteed to have valid req.jacsPayload
  res.send({ data: req.jacsPayload });
});

Content-Type Considerations

JACS requests should use text/plain content type since they are signed JSON strings:

// Client side
const response = await fetch(url, {
  method: 'POST',
  headers: { 'Content-Type': 'text/plain' },  // Not application/json
  body: signedRequest
});
// Server side (Express)
app.use('/api', express.text({ type: '*/*' }));  // Parse as text, not JSON

Error Handling

Server-Side Errors

app.post('/api/process', (req, res, next) => {
  try {
    if (!req.jacsPayload) {
      throw new Error('Missing JACS payload');
    }

    const result = processData(req.jacsPayload);
    res.send({ result });
  } catch (error) {
    next(error);
  }
});

// Global error handler
app.use((error, req, res, next) => {
  console.error('Error:', error.message);
  res.status(500).send({
    error: 'Internal server error',
    message: error.message
  });
});

Client-Side Errors

try {
  const verified = await jacs.verifyResponse(responseText);
  return verified.payload;
} catch (error) {
  console.error('JACS verification failed:', error.message);
  // Handle invalid/tampered response
}

Security Best Practices

1. Use TLS in Production

Always use HTTPS for production deployments:

// Client
await sendJacsRequest('https://api.example.com/data', payload);

2. Separate Server and Client Keys

Each endpoint needs its own JACS identity:

project/
├── server/
│   ├── jacs.config.json
│   └── jacs_keys/
│       ├── private.pem
│       └── public.pem
└── client/
    ├── jacs.config.json
    └── jacs_keys/
        ├── private.pem
        └── public.pem

3. Middleware Order Matters

For Express, ensure correct middleware order:

// Correct order
app.use('/api', express.text({ type: '*/*' }));     // 1. Parse body
app.use('/api', JACSExpressMiddleware({ ... }));    // 2. JACS verification

// Wrong order - JACS won't receive string body
app.use('/api', JACSExpressMiddleware({ ... }));
app.use('/api', express.text({ type: '*/*' }));

4. Avoid JSON Body Parser Conflicts

Don't mix express.json() with JACS routes:

// JSON for non-JACS routes
app.use('/public', express.json());

// Text for JACS routes
app.use('/api', express.text({ type: '*/*' }));
app.use('/api', JACSExpressMiddleware({ ... }));

Logging and Auditing

Log JACS requests for security auditing:

function jacsLogger(req, res, next) {
  if (req.jacsPayload) {
    console.log(JSON.stringify({
      timestamp: new Date().toISOString(),
      method: req.method,
      path: req.path,
      jacsPayload: req.jacsPayload,
      ip: req.ip
    }));
  }
  next();
}

app.use('/api', express.text({ type: '*/*' }));
app.use('/api', JACSExpressMiddleware({ configPath: './jacs.config.json' }));
app.use('/api', jacsLogger);  // After JACS middleware

Testing

Testing with Supertest

import request from 'supertest';
import jacs from 'jacsnpm';

describe('JACS API', () => {
  beforeAll(async () => {
    await jacs.load('./jacs.test.config.json');
  });

  it('should accept valid JACS requests', async () => {
    const payload = { action: 'test', data: 'hello' };
    const signedRequest = await jacs.signRequest(payload);

    const response = await request(app)
      .post('/api/echo')
      .set('Content-Type', 'text/plain')
      .send(signedRequest);

    expect(response.status).toBe(200);

    // Verify response is JACS-signed
    const verified = await jacs.verifyResponse(response.text);
    expect(verified.payload.echo).toEqual(payload);
  });

  it('should reject unsigned requests', async () => {
    const response = await request(app)
      .post('/api/echo')
      .set('Content-Type', 'text/plain')
      .send('{"invalid": "request"}');

    expect(response.status).toBe(400);
  });
});

Troubleshooting

Common Issues

IssueCauseSolution
req.jacsPayload undefinedWrong middleware orderPut express.text() before JACS middleware
Response not signedSending string instead of objectUse res.send({ ... }) not res.send(JSON.stringify(...))
Verification failuresKey mismatchEnsure compatible JACS configurations
Connection refusedServer not runningVerify server is listening on correct port

Debug Logging

// Node.js
process.env.JACS_DEBUG = 'true';
# Python
import logging
logging.basicConfig(level=logging.DEBUG)

See Also

Databases

While JACS provides built-in storage backends (filesystem, S3, HAI Cloud), you may need to integrate JACS documents with traditional databases for querying, indexing, or application-specific requirements.

Overview

JACS documents are JSON objects with cryptographic signatures. They can be stored in any database that supports JSON or text storage:

Database TypeStorage MethodBest For
PostgreSQLJSONB columnComplex queries, relations
MongoDBNative documentsDocument-centric apps
SQLiteTEXT columnLocal/embedded apps
RedisKey-valueCaching, high-speed access

Why Use a Database?

The built-in JACS storage backends are optimized for document integrity and versioning. Use a database when you need:

  • Complex Queries: Search across document fields
  • Indexing: Fast lookups on specific attributes
  • Relations: Link JACS documents to other data
  • Transactions: Atomic operations across multiple documents
  • Existing Infrastructure: Integrate with current systems

PostgreSQL Integration

Schema Design

-- JACS documents table
CREATE TABLE jacs_documents (
    id UUID PRIMARY KEY,
    version_id UUID NOT NULL,
    document JSONB NOT NULL,
    signature_valid BOOLEAN DEFAULT TRUE,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),

    -- Extracted fields for indexing
    agent_id UUID,
    document_type VARCHAR(100),

    UNIQUE(id, version_id)
);

-- Index on JACS fields
CREATE INDEX idx_jacs_agent ON jacs_documents(agent_id);
CREATE INDEX idx_jacs_type ON jacs_documents(document_type);
CREATE INDEX idx_jacs_created ON jacs_documents(created_at);

-- GIN index for JSONB queries
CREATE INDEX idx_jacs_document ON jacs_documents USING GIN (document);

Node.js Example

import { Pool } from 'pg';
import jacs from 'jacsnpm';

const pool = new Pool({
  connectionString: process.env.DATABASE_URL
});

class JacsDocumentStore {
  constructor(configPath) {
    this.configPath = configPath;
  }

  async initialize() {
    await jacs.load(this.configPath);
  }

  async createAndStore(content, options = {}) {
    // Create JACS document
    const docString = await jacs.createDocument(
      JSON.stringify(content),
      options.customSchema
    );

    const doc = JSON.parse(docString);

    // Store in database
    const result = await pool.query(`
      INSERT INTO jacs_documents (id, version_id, document, agent_id, document_type)
      VALUES ($1, $2, $3, $4, $5)
      RETURNING *
    `, [
      doc.jacsId,
      doc.jacsVersion,
      doc,
      doc.jacsSignature?.agentID,
      content.type || 'document'
    ]);

    return result.rows[0];
  }

  async getDocument(id, versionId = null) {
    let query = 'SELECT * FROM jacs_documents WHERE id = $1';
    const params = [id];

    if (versionId) {
      query += ' AND version_id = $2';
      params.push(versionId);
    } else {
      query += ' ORDER BY created_at DESC LIMIT 1';
    }

    const result = await pool.query(query, params);
    return result.rows[0];
  }

  async verifyDocument(id) {
    const row = await this.getDocument(id);
    if (!row) return null;

    const isValid = await jacs.verifyDocument(JSON.stringify(row.document));

    // Update signature_valid flag
    await pool.query(
      'UPDATE jacs_documents SET signature_valid = $1 WHERE id = $2 AND version_id = $3',
      [isValid, row.id, row.version_id]
    );

    return { document: row.document, isValid };
  }

  async searchDocuments(query) {
    const result = await pool.query(`
      SELECT * FROM jacs_documents
      WHERE document @> $1
      ORDER BY created_at DESC
    `, [JSON.stringify(query)]);

    return result.rows;
  }
}

// Usage
const store = new JacsDocumentStore('./jacs.config.json');
await store.initialize();

// Create and store a document
const doc = await store.createAndStore({
  type: 'invoice',
  amount: 1500,
  customer: 'Acme Corp'
});

// Search documents
const invoices = await store.searchDocuments({ type: 'invoice' });

Python Example

import json
import jacs
import psycopg2
from psycopg2.extras import RealDictCursor

class JacsDocumentStore:
    def __init__(self, config_path, database_url):
        self.config_path = config_path
        self.database_url = database_url
        self.conn = None

    def initialize(self):
        # Initialize JACS
        agent = jacs.JacsAgent()
        agent.load(self.config_path)

        # Connect to database
        self.conn = psycopg2.connect(self.database_url)

    def create_and_store(self, content, custom_schema=None):
        # Create JACS document
        doc_string = jacs.create_document(
            json.dumps(content),
            custom_schema=custom_schema
        )
        doc = json.loads(doc_string)

        # Store in database
        with self.conn.cursor(cursor_factory=RealDictCursor) as cur:
            cur.execute("""
                INSERT INTO jacs_documents (id, version_id, document, agent_id, document_type)
                VALUES (%s, %s, %s, %s, %s)
                RETURNING *
            """, (
                doc['jacsId'],
                doc['jacsVersion'],
                json.dumps(doc),
                doc.get('jacsSignature', {}).get('agentID'),
                content.get('type', 'document')
            ))
            self.conn.commit()
            return cur.fetchone()

    def get_document(self, doc_id, version_id=None):
        with self.conn.cursor(cursor_factory=RealDictCursor) as cur:
            if version_id:
                cur.execute(
                    "SELECT * FROM jacs_documents WHERE id = %s AND version_id = %s",
                    (doc_id, version_id)
                )
            else:
                cur.execute(
                    "SELECT * FROM jacs_documents WHERE id = %s ORDER BY created_at DESC LIMIT 1",
                    (doc_id,)
                )
            return cur.fetchone()

    def verify_document(self, doc_id):
        row = self.get_document(doc_id)
        if not row:
            return None

        is_valid = jacs.verify_document(json.dumps(row['document']))

        # Update verification status
        with self.conn.cursor() as cur:
            cur.execute(
                "UPDATE jacs_documents SET signature_valid = %s WHERE id = %s AND version_id = %s",
                (is_valid, row['id'], row['version_id'])
            )
            self.conn.commit()

        return {'document': row['document'], 'is_valid': is_valid}

    def search_documents(self, query):
        with self.conn.cursor(cursor_factory=RealDictCursor) as cur:
            cur.execute(
                "SELECT * FROM jacs_documents WHERE document @> %s ORDER BY created_at DESC",
                (json.dumps(query),)
            )
            return cur.fetchall()

# Usage
store = JacsDocumentStore('./jacs.config.json', 'postgresql://localhost/mydb')
store.initialize()

# Create and store a document
doc = store.create_and_store({
    'type': 'invoice',
    'amount': 1500,
    'customer': 'Acme Corp'
})

# Search documents
invoices = store.search_documents({'type': 'invoice'})

MongoDB Integration

Collection Design

// MongoDB schema (using Mongoose)
const jacsDocumentSchema = new mongoose.Schema({
  jacsId: { type: String, required: true, index: true },
  jacsVersion: { type: String, required: true },
  document: { type: mongoose.Schema.Types.Mixed, required: true },
  signatureValid: { type: Boolean, default: true },
  agentId: { type: String, index: true },
  documentType: { type: String, index: true },
  createdAt: { type: Date, default: Date.now, index: true }
});

jacsDocumentSchema.index({ jacsId: 1, jacsVersion: 1 }, { unique: true });

Example

import mongoose from 'mongoose';
import jacs from 'jacsnpm';

const JacsDocument = mongoose.model('JacsDocument', jacsDocumentSchema);

class MongoJacsStore {
  async initialize(configPath) {
    await jacs.load(configPath);
    await mongoose.connect(process.env.MONGODB_URI);
  }

  async createAndStore(content, options = {}) {
    const docString = await jacs.createDocument(
      JSON.stringify(content),
      options.customSchema
    );

    const doc = JSON.parse(docString);

    const stored = await JacsDocument.create({
      jacsId: doc.jacsId,
      jacsVersion: doc.jacsVersion,
      document: doc,
      agentId: doc.jacsSignature?.agentID,
      documentType: content.type || 'document'
    });

    return stored;
  }

  async getDocument(jacsId, versionId = null) {
    const query = { jacsId };
    if (versionId) {
      query.jacsVersion = versionId;
    }

    return JacsDocument.findOne(query).sort({ createdAt: -1 });
  }

  async searchDocuments(query) {
    // Build MongoDB query from content fields
    const mongoQuery = {};
    for (const [key, value] of Object.entries(query)) {
      mongoQuery[`document.${key}`] = value;
    }

    return JacsDocument.find(mongoQuery).sort({ createdAt: -1 });
  }

  async getDocumentsByAgent(agentId) {
    return JacsDocument.find({ agentId }).sort({ createdAt: -1 });
  }
}

// Usage
const store = new MongoJacsStore();
await store.initialize('./jacs.config.json');

const doc = await store.createAndStore({
  type: 'contract',
  parties: ['Alice', 'Bob'],
  value: 50000
});

const contracts = await store.searchDocuments({ type: 'contract' });

SQLite Integration

For embedded or local applications:

import Database from 'better-sqlite3';
import jacs from 'jacsnpm';

class SqliteJacsStore {
  constructor(dbPath) {
    this.db = new Database(dbPath);
    this.initSchema();
  }

  initSchema() {
    this.db.exec(`
      CREATE TABLE IF NOT EXISTS jacs_documents (
        id TEXT NOT NULL,
        version_id TEXT NOT NULL,
        document TEXT NOT NULL,
        agent_id TEXT,
        document_type TEXT,
        signature_valid INTEGER DEFAULT 1,
        created_at TEXT DEFAULT CURRENT_TIMESTAMP,
        PRIMARY KEY (id, version_id)
      );

      CREATE INDEX IF NOT EXISTS idx_agent ON jacs_documents(agent_id);
      CREATE INDEX IF NOT EXISTS idx_type ON jacs_documents(document_type);
    `);
  }

  async initialize(configPath) {
    await jacs.load(configPath);
  }

  async createAndStore(content, options = {}) {
    const docString = await jacs.createDocument(
      JSON.stringify(content),
      options.customSchema
    );

    const doc = JSON.parse(docString);

    const stmt = this.db.prepare(`
      INSERT INTO jacs_documents (id, version_id, document, agent_id, document_type)
      VALUES (?, ?, ?, ?, ?)
    `);

    stmt.run(
      doc.jacsId,
      doc.jacsVersion,
      docString,
      doc.jacsSignature?.agentID,
      content.type || 'document'
    );

    return doc;
  }

  getDocument(id, versionId = null) {
    let query = 'SELECT * FROM jacs_documents WHERE id = ?';
    const params = [id];

    if (versionId) {
      query += ' AND version_id = ?';
      params.push(versionId);
    } else {
      query += ' ORDER BY created_at DESC LIMIT 1';
    }

    const stmt = this.db.prepare(query);
    const row = stmt.get(...params);

    if (row) {
      row.document = JSON.parse(row.document);
    }

    return row;
  }

  searchByType(documentType) {
    const stmt = this.db.prepare(
      'SELECT * FROM jacs_documents WHERE document_type = ? ORDER BY created_at DESC'
    );
    return stmt.all(documentType).map(row => ({
      ...row,
      document: JSON.parse(row.document)
    }));
  }
}

Redis Caching

Use Redis for high-speed document access:

import Redis from 'ioredis';
import jacs from 'jacsnpm';

class JacsRedisCache {
  constructor(redisUrl) {
    this.redis = new Redis(redisUrl);
    this.ttl = 3600; // 1 hour default TTL
  }

  async initialize(configPath) {
    await jacs.load(configPath);
  }

  async cacheDocument(docString) {
    const doc = JSON.parse(docString);
    const key = `jacs:${doc.jacsId}:${doc.jacsVersion}`;

    await this.redis.setex(key, this.ttl, docString);

    // Also cache as latest version
    await this.redis.setex(`jacs:${doc.jacsId}:latest`, this.ttl, docString);

    return doc;
  }

  async getDocument(jacsId, versionId = 'latest') {
    const key = `jacs:${jacsId}:${versionId}`;
    const docString = await this.redis.get(key);

    if (!docString) return null;

    return JSON.parse(docString);
  }

  async invalidate(jacsId, versionId = null) {
    if (versionId) {
      await this.redis.del(`jacs:${jacsId}:${versionId}`);
    } else {
      // Invalidate all versions
      const keys = await this.redis.keys(`jacs:${jacsId}:*`);
      if (keys.length > 0) {
        await this.redis.del(...keys);
      }
    }
  }
}

Indexing Strategies

Extracting Searchable Fields

Extract key fields during storage for efficient querying:

async function extractIndexFields(jacsDocument) {
  return {
    jacsId: jacsDocument.jacsId,
    jacsVersion: jacsDocument.jacsVersion,
    agentId: jacsDocument.jacsSignature?.agentID,
    createdDate: jacsDocument.jacsSignature?.date,
    hasAgreement: !!jacsDocument.jacsAgreement,
    agreementComplete: jacsDocument.jacsAgreement?.signatures?.length ===
                        jacsDocument.jacsAgreement?.agentIDs?.length,
    // Application-specific fields
    documentType: jacsDocument.type,
    status: jacsDocument.status,
    tags: jacsDocument.tags || []
  };
}

PostgreSQL example with full-text search:

-- Add text search column
ALTER TABLE jacs_documents ADD COLUMN search_vector tsvector;

-- Create index
CREATE INDEX idx_search ON jacs_documents USING GIN(search_vector);

-- Update trigger
CREATE OR REPLACE FUNCTION update_search_vector() RETURNS trigger AS $$
BEGIN
  NEW.search_vector := to_tsvector('english',
    coalesce(NEW.document->>'title', '') || ' ' ||
    coalesce(NEW.document->>'content', '') || ' ' ||
    coalesce(NEW.document->>'description', '')
  );
  RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER jacs_search_update
  BEFORE INSERT OR UPDATE ON jacs_documents
  FOR EACH ROW EXECUTE FUNCTION update_search_vector();
// Search example
async function fullTextSearch(searchQuery) {
  const result = await pool.query(`
    SELECT *, ts_rank(search_vector, plainto_tsquery($1)) as rank
    FROM jacs_documents
    WHERE search_vector @@ plainto_tsquery($1)
    ORDER BY rank DESC
  `, [searchQuery]);

  return result.rows;
}

Best Practices

1. Store Complete Documents

Always store the complete JACS document to preserve signatures:

// Good - stores complete document
await pool.query(
  'INSERT INTO jacs_documents (id, document) VALUES ($1, $2)',
  [doc.jacsId, JSON.stringify(doc)]  // Complete document
);

// Bad - loses signature data
await pool.query(
  'INSERT INTO documents (id, content) VALUES ($1, $2)',
  [doc.jacsId, JSON.stringify(doc.content)]  // Only content
);

2. Verify After Retrieval

Always verify signatures when retrieving documents for sensitive operations:

async function getVerifiedDocument(id) {
  const row = await pool.query(
    'SELECT document FROM jacs_documents WHERE id = $1',
    [id]
  );

  if (!row.rows[0]) return null;

  const docString = JSON.stringify(row.rows[0].document);
  const isValid = await jacs.verifyDocument(docString);

  if (!isValid) {
    throw new Error('Document signature verification failed');
  }

  return row.rows[0].document;
}

3. Handle Version History

Maintain version history for audit trails:

async function getDocumentHistory(jacsId) {
  const result = await pool.query(`
    SELECT * FROM jacs_documents
    WHERE id = $1
    ORDER BY created_at ASC
  `, [jacsId]);

  return result.rows;
}

4. Batch Verification

Periodically verify stored documents:

async function verifyAllDocuments() {
  const docs = await pool.query('SELECT * FROM jacs_documents');

  for (const row of docs.rows) {
    const docString = JSON.stringify(row.document);
    const isValid = await jacs.verifyDocument(docString);

    if (!isValid) {
      console.warn(`Document ${row.id} failed verification`);
      await pool.query(
        'UPDATE jacs_documents SET signature_valid = false WHERE id = $1',
        [row.id]
      );
    }
  }
}

See Also

CLI Examples

This chapter provides practical examples of using the JACS CLI for common workflows.

Quick Reference

jacs init                  # Initialize JACS (config + agent + keys)
jacs agent create          # Create a new agent
jacs document create       # Create a signed document
jacs document verify       # Verify a document signature
jacs document sign-agreement  # Sign an agreement

Getting Started

First-Time Setup

Initialize JACS in a new project:

# Create a new directory
mkdir my-jacs-project
cd my-jacs-project

# Initialize JACS
jacs init

# This creates:
# - jacs.config.json (configuration)
# - jacs_keys/ (private and public keys)
# - jacs_data/ (document storage)
# - An initial agent document

Verify Your Setup

# Check the configuration
jacs config read

# Verify your agent
jacs agent verify

# Expected output:
# Agent verification successful
# Agent ID: 550e8400-e29b-41d4-a716-446655440000
# Agent Version: f47ac10b-58cc-4372-a567-0e02b2c3d479

Document Operations

Creating Documents

Create from a JSON file:

# Create input file
cat > invoice.json << 'EOF'
{
  "type": "invoice",
  "invoiceNumber": "INV-001",
  "customer": "Acme Corp",
  "amount": 1500.00,
  "items": [
    {"description": "Consulting", "quantity": 10, "price": 150}
  ]
}
EOF

# Create signed document
jacs document create -f invoice.json

# Output shows the saved document path
# Document saved to: jacs_data/documents/[uuid]/[version].json

Create with custom output:

# Specify output filename
jacs document create -f invoice.json -o signed-invoice.json

# Print to stdout (don't save)
jacs document create -f invoice.json --no-save

Create with file attachments:

# Create document with PDF attachment
jacs document create -f contract.json --attach ./contract.pdf

# Embed attachment content in document
jacs document create -f contract.json --attach ./contract.pdf --embed true

# Attach entire directory
jacs document create -f report.json --attach ./attachments/

Create with custom schema:

# Use a custom schema for validation
jacs document create -f order.json -s ./schemas/order.schema.json

Verifying Documents

Basic verification:

# Verify a document
jacs document verify -f ./signed-invoice.json

# Expected output:
# Document verified successfully
# Document ID: 550e8400-e29b-41d4-a716-446655440000
# Signer: Agent Name (agent-uuid)

Verbose verification:

# Get detailed verification info
jacs document verify -f ./signed-invoice.json -v

# Output includes:
# - Document ID and version
# - Signature algorithm used
# - Signing agent details
# - Timestamp
# - Schema validation results

Batch verification:

# Verify all documents in a directory
jacs document verify -d ./documents/

# With custom schema
jacs document verify -d ./invoices/ -s ./schemas/invoice.schema.json

Updating Documents

Create a new version of an existing document:

# Original document
cat > original.json << 'EOF'
{
  "title": "Project Plan",
  "status": "draft",
  "content": "Initial version"
}
EOF

jacs document create -f original.json -o project-v1.json

# Updated content
cat > updated.json << 'EOF'
{
  "title": "Project Plan",
  "status": "approved",
  "content": "Final version with updates"
}
EOF

# Create new version (maintains version history)
jacs document update -f project-v1.json -n updated.json -o project-v2.json

# Verify the updated document
jacs document verify -f project-v2.json -v

Extracting Embedded Content

# Extract embedded files from a document
jacs document extract -f ./document-with-attachments.json

# Extracts to: jacs_data/extracted/[document-id]/

# Extract from multiple documents
jacs document extract -d ./documents/

Agreement Workflows

Creating an Agreement

An agreement requires multiple agents to sign a document:

# First, create the document to be agreed upon
cat > service-agreement.json << 'EOF'
{
  "type": "service_agreement",
  "title": "Professional Services Agreement",
  "parties": ["Company A", "Company B"],
  "terms": "...",
  "effectiveDate": "2024-02-01"
}
EOF

jacs document create -f service-agreement.json -o agreement.json

# Create agreement requiring signatures from two agents
# (Use actual agent UUIDs)
jacs document create-agreement \
  -f agreement.json \
  -i "agent1-uuid-here,agent2-uuid-here" \
  -o agreement-pending.json

# Output:
# Agreement created
# Required signatures: 2
# Current signatures: 0

Signing an Agreement

# First agent signs
jacs document sign-agreement -f agreement-pending.json -o agreement-signed-1.json

# Check status
jacs document check-agreement -f agreement-signed-1.json
# Output:
# Agreement status: pending
# Signatures: 1/2
# Missing: agent2-uuid

# Second agent signs (using their configuration)
JACS_CONFIG_PATH=./agent2.config.json \
  jacs document sign-agreement -f agreement-signed-1.json -o agreement-complete.json

# Verify completion
jacs document check-agreement -f agreement-complete.json
# Output:
# Agreement status: complete
# Signatures: 2/2

Complete Agreement Workflow

#!/bin/bash
# agreement-workflow.sh

# Step 1: Create the contract document
cat > contract.json << 'EOF'
{
  "type": "contract",
  "parties": {
    "seller": "Widget Corp",
    "buyer": "Acme Inc"
  },
  "terms": "Sale of 1000 widgets at $10 each",
  "totalValue": 10000
}
EOF

echo "Creating contract document..."
jacs document create -f contract.json -o contract-signed.json

# Step 2: Get agent IDs
SELLER_AGENT=$(jacs config read | grep agent_id | cut -d: -f2 | tr -d ' ')
BUYER_AGENT="buyer-agent-uuid-here"  # Replace with actual ID

# Step 3: Create agreement
echo "Creating agreement..."
jacs document create-agreement \
  -f contract-signed.json \
  -i "$SELLER_AGENT,$BUYER_AGENT" \
  -o contract-agreement.json

# Step 4: Seller signs
echo "Seller signing..."
jacs document sign-agreement \
  -f contract-agreement.json \
  -o contract-seller-signed.json

# Step 5: Check intermediate status
echo "Checking status..."
jacs document check-agreement -f contract-seller-signed.json

# Step 6: Buyer signs
echo "Buyer signing..."
JACS_CONFIG_PATH=./buyer.config.json \
  jacs document sign-agreement \
  -f contract-seller-signed.json \
  -o contract-complete.json

# Step 7: Verify complete agreement
echo "Final verification..."
jacs document verify -f contract-complete.json -v
jacs document check-agreement -f contract-complete.json

echo "Agreement workflow complete!"

Agent Operations

Creating a Custom Agent

# Create agent definition file
cat > my-agent.json << 'EOF'
{
  "jacsAgentType": "ai",
  "name": "My Custom Agent",
  "description": "An AI agent for document processing",
  "contact": {
    "email": "agent@example.com"
  },
  "services": [
    {
      "name": "document-processing",
      "description": "Process and sign documents"
    }
  ]
}
EOF

# Create agent with new keys
jacs agent create --create-keys true -f my-agent.json

# Create agent using existing keys
jacs agent create --create-keys false -f my-agent.json

DNS-Based Identity

Generate DNS record commands:

# Generate TXT record for your domain
jacs agent dns --domain myagent.example.com

# Output (example):
# Add the following DNS TXT record:
# _v1.agent.jacs.myagent.example.com TXT "pk=<base64-public-key-hash>"

# Different providers
jacs agent dns --domain myagent.example.com --provider aws
jacs agent dns --domain myagent.example.com --provider cloudflare
jacs agent dns --domain myagent.example.com --provider azure

# Custom TTL
jacs agent dns --domain myagent.example.com --ttl 7200

Verify DNS-published agent:

# Look up agent by domain
jacs agent lookup partner.example.com

# Require strict DNSSEC validation
jacs agent lookup partner.example.com --strict

# Verify local agent file against DNS
jacs agent verify -a ./partner-agent.json --require-strict-dns

Agent Verification

# Basic verification
jacs agent verify

# Verify another agent's file
jacs agent verify -a ./other-agent.json

# With DNS requirements
jacs agent verify --require-dns          # Require DNS (not strict)
jacs agent verify --require-strict-dns   # Require DNSSEC
jacs agent verify --no-dns              # Skip DNS entirely
jacs agent verify --ignore-dns          # Ignore DNS validation failures

Task Management

Creating Tasks

# Simple task
jacs task create \
  -n "Review Contract" \
  -d "Review the service contract and provide feedback"

# Task with additional data from file
cat > task-details.json << 'EOF'
{
  "priority": "high",
  "dueDate": "2024-02-15",
  "assignee": "legal-team"
}
EOF

jacs task create \
  -n "Contract Review" \
  -d "Detailed review required" \
  -f task-details.json

Scripting Examples

Batch Document Processing

#!/bin/bash
# batch-sign.sh - Sign all JSON files in a directory

INPUT_DIR=$1
OUTPUT_DIR=${2:-"./signed"}

mkdir -p "$OUTPUT_DIR"

for file in "$INPUT_DIR"/*.json; do
  filename=$(basename "$file")
  echo "Signing: $filename"

  jacs document create -f "$file" -o "$OUTPUT_DIR/$filename"

  if [ $? -eq 0 ]; then
    echo "  ✓ Signed successfully"
  else
    echo "  ✗ Signing failed"
  fi
done

echo "Batch signing complete. Output in $OUTPUT_DIR"

Verification Report

#!/bin/bash
# verify-report.sh - Generate verification report

DOC_DIR=$1
REPORT="verification-report.txt"

echo "JACS Document Verification Report" > $REPORT
echo "Generated: $(date)" >> $REPORT
echo "=================================" >> $REPORT
echo "" >> $REPORT

passed=0
failed=0

for file in "$DOC_DIR"/*.json; do
  filename=$(basename "$file")

  if jacs document verify -f "$file" > /dev/null 2>&1; then
    echo "✓ PASS: $filename" >> $REPORT
    ((passed++))
  else
    echo "✗ FAIL: $filename" >> $REPORT
    ((failed++))
  fi
done

echo "" >> $REPORT
echo "Summary: $passed passed, $failed failed" >> $REPORT

cat $REPORT

Watch for New Documents

#!/bin/bash
# watch-and-verify.sh - Monitor directory and verify new documents

WATCH_DIR=${1:-"./incoming"}

echo "Watching $WATCH_DIR for new documents..."

inotifywait -m "$WATCH_DIR" -e create -e moved_to |
  while read dir action file; do
    if [[ "$file" == *.json ]]; then
      echo "New document: $file"

      if jacs document verify -f "$WATCH_DIR/$file"; then
        mv "$WATCH_DIR/$file" "./verified/"
        echo "  Moved to verified/"
      else
        mv "$WATCH_DIR/$file" "./rejected/"
        echo "  Moved to rejected/"
      fi
    fi
  done

Environment Configuration

Using Environment Variables

# Use a specific config file
export JACS_CONFIG_PATH=./production.config.json
jacs document create -f invoice.json

# Override specific settings
export JACS_DATA_DIRECTORY=./custom-data
export JACS_KEY_DIRECTORY=./secure-keys
jacs agent create --create-keys true

# One-time override
JACS_CONFIG_PATH=./test.config.json jacs document verify -f test-doc.json

Multiple Configurations

# Development
alias jacs-dev='JACS_CONFIG_PATH=./dev.config.json jacs'
jacs-dev document create -f test.json

# Production
alias jacs-prod='JACS_CONFIG_PATH=./prod.config.json jacs'
jacs-prod document verify -f important.json

# Different agents
alias jacs-alice='JACS_CONFIG_PATH=./alice.config.json jacs'
alias jacs-bob='JACS_CONFIG_PATH=./bob.config.json jacs'

Error Handling

Understanding Exit Codes

jacs document verify -f document.json
exit_code=$?

case $exit_code in
  0) echo "Success" ;;
  1) echo "General error" ;;
  2) echo "Invalid arguments" ;;
  3) echo "File not found" ;;
  4) echo "Verification failed" ;;
  5) echo "Signature invalid" ;;
  *) echo "Unknown error: $exit_code" ;;
esac

Handling Failures

#!/bin/bash
# robust-signing.sh

sign_document() {
  local input=$1
  local output=$2

  if ! jacs document create -f "$input" -o "$output" 2>/dev/null; then
    echo "Error: Failed to sign $input" >&2
    return 1
  fi

  if ! jacs document verify -f "$output" 2>/dev/null; then
    echo "Error: Verification failed for $output" >&2
    rm -f "$output"
    return 1
  fi

  echo "Successfully signed: $output"
  return 0
}

# Usage
sign_document "invoice.json" "signed-invoice.json" || exit 1

See Also

Node.js Examples

This chapter provides practical Node.js examples using the jacsnpm package.

Setup

# Install dependencies
npm install jacsnpm express @modelcontextprotocol/sdk zod
// Initialize JACS (ES Modules)
import { JacsAgent } from 'jacsnpm';

const agent = new JacsAgent();
await agent.load('./jacs.config.json');

Basic Document Operations

Creating and Signing Documents

import { JacsAgent } from 'jacsnpm';

async function createSignedDocument() {
  const agent = new JacsAgent();
  await agent.load('./jacs.config.json');

  // Create a simple document
  const content = {
    title: 'Invoice',
    invoiceNumber: 'INV-001',
    amount: 1500.00,
    customer: 'Acme Corp',
    items: [
      { description: 'Consulting', quantity: 10, price: 150 }
    ]
  };

  // Create and sign the document
  const signedDoc = await agent.createDocument(JSON.stringify(content));

  // Parse the result
  const doc = JSON.parse(signedDoc);
  console.log('Document ID:', doc.jacsId);
  console.log('Version:', doc.jacsVersion);
  console.log('Signature:', doc.jacsSignature ? 'Present' : 'Missing');

  return doc;
}

createSignedDocument();

Verifying Documents

import { JacsAgent } from 'jacsnpm';
import fs from 'fs';

async function verifyDocument(filePath) {
  const agent = new JacsAgent();
  await agent.load('./jacs.config.json');

  // Read the document
  const docString = fs.readFileSync(filePath, 'utf-8');

  // Verify signature
  const isValid = await agent.verifyDocument(docString);

  if (isValid) {
    console.log('✓ Document signature is valid');
    const doc = JSON.parse(docString);
    console.log('  Signed by:', doc.jacsSignature?.agentID);
    console.log('  Signed at:', doc.jacsSignature?.date);
  } else {
    console.log('✗ Document signature is INVALID');
  }

  return isValid;
}

verifyDocument('./invoice.json');

Updating Documents

import { JacsAgent } from 'jacsnpm';
import fs from 'fs';

async function updateDocument(originalPath, newContent) {
  const agent = new JacsAgent();
  await agent.load('./jacs.config.json');

  // Read original document
  const originalDoc = fs.readFileSync(originalPath, 'utf-8');

  // Update with new content (preserves version chain)
  const updatedDoc = await agent.updateDocument(
    originalDoc,
    JSON.stringify(newContent)
  );

  const doc = JSON.parse(updatedDoc);
  console.log('Updated Document ID:', doc.jacsId);
  console.log('New Version:', doc.jacsVersion);

  return doc;
}

// Usage
const updated = await updateDocument('./invoice-v1.json', {
  title: 'Invoice',
  invoiceNumber: 'INV-001',
  amount: 1500.00,
  customer: 'Acme Corp',
  status: 'paid'  // New field
});

HTTP Server with Express

Complete Express Server

import express from 'express';
import { JACSExpressMiddleware } from 'jacsnpm/http';
import { JacsAgent } from 'jacsnpm';

const app = express();
const PORT = 3000;

// Initialize JACS
const agent = new JacsAgent();
await agent.load('./jacs.config.json');

// Health check (no JACS)
app.get('/health', (req, res) => {
  res.json({ status: 'ok', timestamp: new Date().toISOString() });
});

// JACS-protected API routes
app.use('/api', express.text({ type: '*/*' }));
app.use('/api', JACSExpressMiddleware({
  configPath: './jacs.config.json'
}));

// Validation middleware
function requirePayload(req, res, next) {
  if (!req.jacsPayload) {
    return res.status(400).json({
      error: 'Invalid JACS request',
      message: 'Request must be signed with valid JACS credentials'
    });
  }
  next();
}

// Echo endpoint
app.post('/api/echo', requirePayload, (req, res) => {
  res.send({
    echo: req.jacsPayload,
    serverTime: new Date().toISOString()
  });
});

// Calculate endpoint
app.post('/api/calculate', requirePayload, (req, res) => {
  const { operation, a, b } = req.jacsPayload;

  let result;
  switch (operation) {
    case 'add': result = a + b; break;
    case 'subtract': result = a - b; break;
    case 'multiply': result = a * b; break;
    case 'divide': result = b !== 0 ? a / b : null; break;
    default:
      return res.status(400).send({ error: 'Unknown operation' });
  }

  res.send({ operation, a, b, result });
});

// Create document endpoint
app.post('/api/documents', requirePayload, async (req, res) => {
  try {
    const signedDoc = await agent.createDocument(
      JSON.stringify(req.jacsPayload)
    );
    const doc = JSON.parse(signedDoc);

    res.send({
      success: true,
      documentId: doc.jacsId,
      version: doc.jacsVersion
    });
  } catch (error) {
    res.status(500).send({ error: error.message });
  }
});

// Error handler
app.use((err, req, res, next) => {
  console.error('Error:', err);
  res.status(500).send({ error: 'Internal server error' });
});

app.listen(PORT, () => {
  console.log(`JACS Express server running on port ${PORT}`);
});

HTTP Client

import { JacsAgent } from 'jacsnpm';

async function callJacsApi(url, payload) {
  const agent = new JacsAgent();
  await agent.load('./jacs.client.config.json');

  // Sign the request
  const signedRequest = await agent.signRequest(payload);

  // Send HTTP request
  const response = await fetch(url, {
    method: 'POST',
    headers: { 'Content-Type': 'text/plain' },
    body: signedRequest
  });

  if (!response.ok) {
    throw new Error(`HTTP ${response.status}`);
  }

  // Verify and extract response
  const responseText = await response.text();
  const verified = await agent.verifyResponse(responseText);

  return verified.payload;
}

// Usage
async function main() {
  // Call echo endpoint
  const echoResult = await callJacsApi(
    'http://localhost:3000/api/echo',
    { message: 'Hello, server!' }
  );
  console.log('Echo:', echoResult);

  // Call calculate endpoint
  const calcResult = await callJacsApi(
    'http://localhost:3000/api/calculate',
    { operation: 'multiply', a: 7, b: 6 }
  );
  console.log('Calculate:', calcResult);
}

main().catch(console.error);

MCP Integration

MCP Server

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { createJACSTransportProxy } from 'jacsnpm/mcp';
import { z } from 'zod';

const JACS_CONFIG = "./jacs.server.config.json";

async function main() {
  console.error("JACS MCP Server starting...");

  // Create transport with JACS encryption
  const baseTransport = new StdioServerTransport();
  const secureTransport = createJACSTransportProxy(
    baseTransport,
    JACS_CONFIG,
    "server"
  );

  // Create MCP server
  const server = new McpServer({
    name: "jacs-demo-server",
    version: "1.0.0"
  });

  // Register tools
  server.tool("echo", {
    message: z.string().describe("Message to echo")
  }, async ({ message }) => {
    console.error(`Echo called: ${message}`);
    return { content: [{ type: "text", text: `Echo: ${message}` }] };
  });

  server.tool("calculate", {
    operation: z.enum(["add", "subtract", "multiply", "divide"]),
    a: z.number(),
    b: z.number()
  }, async ({ operation, a, b }) => {
    let result;
    switch (operation) {
      case 'add': result = a + b; break;
      case 'subtract': result = a - b; break;
      case 'multiply': result = a * b; break;
      case 'divide': result = b !== 0 ? a / b : 'undefined'; break;
    }
    return { content: [{ type: "text", text: `${a} ${operation} ${b} = ${result}` }] };
  });

  // Register resource
  server.resource(
    "server-info",
    "info://server",
    async (uri) => ({
      contents: [{
        uri: uri.href,
        text: JSON.stringify({
          name: "JACS Demo Server",
          version: "1.0.0",
          capabilities: ["echo", "calculate"]
        }),
        mimeType: "application/json"
      }]
    })
  );

  // Connect
  await server.connect(secureTransport);
  console.error("Server running with JACS encryption");
}

main().catch(err => {
  console.error("Fatal error:", err);
  process.exit(1);
});

MCP Client

import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
import { createJACSTransportProxy } from 'jacsnpm/mcp';

const JACS_CONFIG = "./jacs.client.config.json";

async function main() {
  console.log("JACS MCP Client starting...");

  // Connect to server
  const baseTransport = new StdioClientTransport({
    command: 'node',
    args: ['mcp-server.js']
  });

  const secureTransport = createJACSTransportProxy(
    baseTransport,
    JACS_CONFIG,
    "client"
  );

  const client = new Client({
    name: "jacs-demo-client",
    version: "1.0.0"
  }, {
    capabilities: { tools: {} }
  });

  await client.connect(secureTransport);
  console.log("Connected to JACS MCP Server");

  // List tools
  const tools = await client.listTools();
  console.log("Available tools:", tools.tools.map(t => t.name));

  // Call echo
  const echoResult = await client.callTool({
    name: "echo",
    arguments: { message: "Hello, JACS!" }
  });
  console.log("Echo:", echoResult.content[0].text);

  // Call calculate
  const calcResult = await client.callTool({
    name: "calculate",
    arguments: { operation: "multiply", a: 6, b: 7 }
  });
  console.log("Calculate:", calcResult.content[0].text);

  await client.close();
  console.log("Done!");
}

main().catch(console.error);

Agreements

Creating Multi-Party Agreements

import { JacsAgent } from 'jacsnpm';
import fs from 'fs';

async function createAgreement() {
  const agent = new JacsAgent();
  await agent.load('./jacs.config.json');

  // Create the contract document
  const contract = {
    type: 'service_agreement',
    title: 'Professional Services Agreement',
    parties: ['Company A', 'Company B'],
    terms: 'Terms and conditions here...',
    value: 50000,
    effectiveDate: '2024-02-01'
  };

  const signedContract = await agent.createDocument(JSON.stringify(contract));

  // Get agent IDs (replace with actual UUIDs)
  const agentIds = [
    'agent1-uuid-here',
    'agent2-uuid-here'
  ];

  // Create agreement
  const agreementDoc = await agent.createAgreement(
    signedContract,
    agentIds,
    'Do you agree to the terms of this service agreement?',
    'This is a legally binding agreement'
  );

  console.log('Agreement created');
  const doc = JSON.parse(agreementDoc);
  console.log('Document ID:', doc.jacsId);
  console.log('Required signatures:', doc.jacsAgreement?.agentIDs?.length);

  // Save for signing
  fs.writeFileSync('agreement-pending.json', agreementDoc);

  return doc;
}

createAgreement();

Signing Agreements

import { JacsAgent } from 'jacsnpm';
import fs from 'fs';

async function signAgreement(agreementPath, outputPath) {
  const agent = new JacsAgent();
  await agent.load('./jacs.config.json');

  // Read agreement
  const agreementDoc = fs.readFileSync(agreementPath, 'utf-8');

  // Sign agreement
  const signedAgreement = await agent.signAgreement(agreementDoc);

  // Check status
  const statusJson = await agent.checkAgreement(signedAgreement);
  const status = JSON.parse(statusJson);

  console.log('Agreement signed');
  console.log('Status:', status.complete ? 'Complete' : 'Pending');
  console.log('Signatures:', status.signatures?.length || 0);

  // Save
  fs.writeFileSync(outputPath, signedAgreement);

  return status;
}

signAgreement('./agreement-pending.json', './agreement-signed.json');

Checking Agreement Status

import { JacsAgent } from 'jacsnpm';
import fs from 'fs';

async function checkAgreementStatus(agreementPath) {
  const agent = new JacsAgent();
  await agent.load('./jacs.config.json');

  const agreementDoc = fs.readFileSync(agreementPath, 'utf-8');
  const statusJson = await agent.checkAgreement(agreementDoc);
  const status = JSON.parse(statusJson);

  console.log('Agreement Status:');
  console.log('  Complete:', status.complete);
  console.log('  Required agents:', status.requiredAgents);
  console.log('  Signed by:', status.signedBy || []);
  console.log('  Missing:', status.missing || []);

  return status;
}

checkAgreementStatus('./agreement.json');

Document Store

Simple File-Based Store

import { JacsAgent } from 'jacsnpm';
import fs from 'fs';
import path from 'path';

class JacsDocumentStore {
  constructor(configPath, dataDir = './documents') {
    this.configPath = configPath;
    this.dataDir = dataDir;
    this.agent = null;
  }

  async initialize() {
    this.agent = new JacsAgent();
    await this.agent.load(this.configPath);

    if (!fs.existsSync(this.dataDir)) {
      fs.mkdirSync(this.dataDir, { recursive: true });
    }
  }

  async create(content) {
    const signedDoc = await this.agent.createDocument(JSON.stringify(content));
    const doc = JSON.parse(signedDoc);

    const filename = `${doc.jacsId}.json`;
    const filepath = path.join(this.dataDir, filename);

    fs.writeFileSync(filepath, signedDoc);

    return { id: doc.jacsId, version: doc.jacsVersion, path: filepath };
  }

  async get(documentId) {
    const filepath = path.join(this.dataDir, `${documentId}.json`);

    if (!fs.existsSync(filepath)) {
      return null;
    }

    const docString = fs.readFileSync(filepath, 'utf-8');
    return JSON.parse(docString);
  }

  async verify(documentId) {
    const filepath = path.join(this.dataDir, `${documentId}.json`);

    if (!fs.existsSync(filepath)) {
      return { valid: false, error: 'Document not found' };
    }

    const docString = fs.readFileSync(filepath, 'utf-8');
    const isValid = await this.agent.verifyDocument(docString);

    return { valid: isValid, document: JSON.parse(docString) };
  }

  list() {
    const files = fs.readdirSync(this.dataDir);
    return files
      .filter(f => f.endsWith('.json'))
      .map(f => f.replace('.json', ''));
  }
}

// Usage
async function main() {
  const store = new JacsDocumentStore('./jacs.config.json');
  await store.initialize();

  // Create document
  const result = await store.create({
    type: 'note',
    title: 'Meeting Notes',
    content: 'Discussed project timeline...'
  });
  console.log('Created:', result.id);

  // Verify document
  const verification = await store.verify(result.id);
  console.log('Valid:', verification.valid);

  // List all documents
  const docs = store.list();
  console.log('Documents:', docs);
}

main();

Error Handling

Robust Error Handling Pattern

import { JacsAgent } from 'jacsnpm';

class JacsError extends Error {
  constructor(message, code, details = {}) {
    super(message);
    this.name = 'JacsError';
    this.code = code;
    this.details = details;
  }
}

async function robustDocumentCreate(configPath, content) {
  let agent;

  try {
    agent = new JacsAgent();
    await agent.load(configPath);
  } catch (error) {
    throw new JacsError(
      'Failed to initialize JACS agent',
      'INIT_ERROR',
      { originalError: error.message }
    );
  }

  try {
    const signedDoc = await agent.createDocument(JSON.stringify(content));
    return JSON.parse(signedDoc);
  } catch (error) {
    throw new JacsError(
      'Failed to create document',
      'CREATE_ERROR',
      { originalError: error.message, content }
    );
  }
}

async function robustDocumentVerify(configPath, docString) {
  let agent;

  try {
    agent = new JacsAgent();
    await agent.load(configPath);
  } catch (error) {
    throw new JacsError(
      'Failed to initialize JACS agent',
      'INIT_ERROR',
      { originalError: error.message }
    );
  }

  try {
    const isValid = await agent.verifyDocument(docString);
    return { valid: isValid };
  } catch (error) {
    throw new JacsError(
      'Verification error',
      'VERIFY_ERROR',
      { originalError: error.message }
    );
  }
}

// Usage with error handling
async function main() {
  try {
    const doc = await robustDocumentCreate('./jacs.config.json', {
      title: 'Test'
    });
    console.log('Created:', doc.jacsId);
  } catch (error) {
    if (error instanceof JacsError) {
      console.error(`JACS Error [${error.code}]:`, error.message);
      console.error('Details:', error.details);
    } else {
      console.error('Unexpected error:', error);
    }
  }
}

main();

Testing

Jest Test Setup

// tests/jacs.test.js
import { JacsAgent } from 'jacsnpm';
import fs from 'fs';
import path from 'path';
import os from 'os';

describe('JACS Document Operations', () => {
  let agent;
  let tempDir;
  let configPath;

  beforeAll(async () => {
    // Create temp directory
    tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'jacs-test-'));
    const dataDir = path.join(tempDir, 'data');
    const keyDir = path.join(tempDir, 'keys');

    fs.mkdirSync(dataDir);
    fs.mkdirSync(keyDir);

    // Create test config
    const config = {
      jacs_data_directory: dataDir,
      jacs_key_directory: keyDir,
      jacs_agent_key_algorithm: 'ring-Ed25519',
      jacs_default_storage: 'fs'
    };

    configPath = path.join(tempDir, 'jacs.config.json');
    fs.writeFileSync(configPath, JSON.stringify(config));

    // Initialize agent
    agent = new JacsAgent();
    await agent.load(configPath);
  });

  afterAll(() => {
    fs.rmSync(tempDir, { recursive: true });
  });

  test('creates a signed document', async () => {
    const content = { title: 'Test Document', value: 42 };
    const signedDoc = await agent.createDocument(JSON.stringify(content));
    const doc = JSON.parse(signedDoc);

    expect(doc.jacsId).toBeDefined();
    expect(doc.jacsVersion).toBeDefined();
    expect(doc.jacsSignature).toBeDefined();
    expect(doc.title).toBe('Test Document');
  });

  test('verifies a valid document', async () => {
    const content = { title: 'Verify Test' };
    const signedDoc = await agent.createDocument(JSON.stringify(content));

    const isValid = await agent.verifyDocument(signedDoc);
    expect(isValid).toBe(true);
  });

  test('detects tampered document', async () => {
    const content = { title: 'Tamper Test' };
    const signedDoc = await agent.createDocument(JSON.stringify(content));

    // Tamper with document
    const doc = JSON.parse(signedDoc);
    doc.title = 'Modified Title';
    const tamperedDoc = JSON.stringify(doc);

    const isValid = await agent.verifyDocument(tamperedDoc);
    expect(isValid).toBe(false);
  });
});

See Also

Python Examples

This chapter provides practical Python examples using the jacs (jacspy) package.

Setup

# Install dependencies
pip install jacs fastmcp fastapi uvicorn
# Initialize JACS
import jacs

agent = jacs.JacsAgent()
agent.load('./jacs.config.json')

Basic Document Operations

Creating and Signing Documents

import jacs
import json

def create_signed_document():
    # Initialize agent
    agent = jacs.JacsAgent()
    agent.load('./jacs.config.json')

    # Create document content
    content = {
        "title": "Invoice",
        "invoiceNumber": "INV-001",
        "amount": 1500.00,
        "customer": "Acme Corp",
        "items": [
            {"description": "Consulting", "quantity": 10, "price": 150}
        ]
    }

    # Create and sign the document
    signed_doc = agent.create_document(json.dumps(content))

    # Parse the result
    doc = json.loads(signed_doc)
    print(f"Document ID: {doc['jacsId']}")
    print(f"Version: {doc['jacsVersion']}")
    print(f"Signature: {'Present' if 'jacsSignature' in doc else 'Missing'}")

    return doc

if __name__ == "__main__":
    create_signed_document()

Verifying Documents

import jacs
import json

def verify_document(file_path: str) -> bool:
    # Initialize agent
    agent = jacs.JacsAgent()
    agent.load('./jacs.config.json')

    # Read the document
    with open(file_path, 'r') as f:
        doc_string = f.read()

    # Verify signature
    is_valid = agent.verify_document(doc_string)

    if is_valid:
        doc = json.loads(doc_string)
        print("✓ Document signature is valid")
        print(f"  Signed by: {doc.get('jacsSignature', {}).get('agentID')}")
        print(f"  Signed at: {doc.get('jacsSignature', {}).get('date')}")
    else:
        print("✗ Document signature is INVALID")

    return is_valid

if __name__ == "__main__":
    verify_document('./invoice.json')

Updating Documents

import jacs
import json

def update_document(original_path: str, new_content: dict) -> dict:
    # Initialize agent
    agent = jacs.JacsAgent()
    agent.load('./jacs.config.json')

    # Read original document
    with open(original_path, 'r') as f:
        original_doc = f.read()

    # Update with new content (preserves version chain)
    updated_doc = agent.update_document(
        original_doc,
        json.dumps(new_content)
    )

    doc = json.loads(updated_doc)
    print(f"Updated Document ID: {doc['jacsId']}")
    print(f"New Version: {doc['jacsVersion']}")

    return doc

if __name__ == "__main__":
    updated = update_document('./invoice-v1.json', {
        "title": "Invoice",
        "invoiceNumber": "INV-001",
        "amount": 1500.00,
        "customer": "Acme Corp",
        "status": "paid"  # New field
    })

HTTP Server with FastAPI

Complete FastAPI Server

from fastapi import FastAPI, Request, HTTPException
from fastapi.responses import PlainTextResponse
import jacs
import json

app = FastAPI(title="JACS API")

# Initialize JACS agent at startup
agent = None

@app.on_event("startup")
async def startup():
    global agent
    agent = jacs.JacsAgent()
    agent.load('./jacs.config.json')

# Health check (no JACS)
@app.get("/health")
async def health():
    return {"status": "ok"}

# JACS-protected endpoint
@app.post("/api/echo")
async def echo(request: Request):
    # Read raw body
    body = await request.body()
    body_str = body.decode('utf-8')

    # Verify JACS request
    try:
        verified = jacs.verify_request(body_str)
        payload = json.loads(verified).get('payload')
    except Exception as e:
        raise HTTPException(status_code=400, detail="Invalid JACS request")

    # Process and respond
    result = {
        "echo": payload,
        "serverTime": str(datetime.now())
    }

    # Sign response
    signed_response = jacs.sign_response(result)
    return PlainTextResponse(content=signed_response)

# Create document endpoint
@app.post("/api/documents")
async def create_document(request: Request):
    body = await request.body()
    body_str = body.decode('utf-8')

    try:
        verified = jacs.verify_request(body_str)
        payload = json.loads(verified).get('payload')
    except Exception as e:
        raise HTTPException(status_code=400, detail="Invalid JACS request")

    # Create signed document
    signed_doc = agent.create_document(json.dumps(payload))
    doc = json.loads(signed_doc)

    result = {
        "success": True,
        "documentId": doc['jacsId'],
        "version": doc['jacsVersion']
    }

    signed_response = jacs.sign_response(result)
    return PlainTextResponse(content=signed_response)

# Calculate endpoint
@app.post("/api/calculate")
async def calculate(request: Request):
    body = await request.body()
    body_str = body.decode('utf-8')

    try:
        verified = jacs.verify_request(body_str)
        payload = json.loads(verified).get('payload')
    except Exception as e:
        raise HTTPException(status_code=400, detail="Invalid JACS request")

    operation = payload.get('operation')
    a = payload.get('a', 0)
    b = payload.get('b', 0)

    if operation == 'add':
        result = a + b
    elif operation == 'subtract':
        result = a - b
    elif operation == 'multiply':
        result = a * b
    elif operation == 'divide':
        result = a / b if b != 0 else None
    else:
        raise HTTPException(status_code=400, detail="Unknown operation")

    response = {"operation": operation, "a": a, "b": b, "result": result}
    signed_response = jacs.sign_response(response)
    return PlainTextResponse(content=signed_response)

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="localhost", port=8000)

HTTP Client

import jacs
import requests
import json

def call_jacs_api(url: str, payload: dict) -> dict:
    # Initialize agent
    agent = jacs.JacsAgent()
    agent.load('./jacs.client.config.json')

    # Sign the request
    signed_request = jacs.sign_request(payload)

    # Send HTTP request
    response = requests.post(
        url,
        data=signed_request,
        headers={"Content-Type": "text/plain"}
    )

    if response.status_code != 200:
        raise Exception(f"HTTP {response.status_code}")

    # Verify and extract response
    verified = jacs.verify_response(response.text)
    return json.loads(verified).get('payload')

if __name__ == "__main__":
    # Call echo endpoint
    echo_result = call_jacs_api(
        'http://localhost:8000/api/echo',
        {"message": "Hello, server!"}
    )
    print("Echo:", echo_result)

    # Call calculate endpoint
    calc_result = call_jacs_api(
        'http://localhost:8000/api/calculate',
        {"operation": "multiply", "a": 7, "b": 6}
    )
    print("Calculate:", calc_result)

MCP Integration

FastMCP Server with JACS

import jacs
from jacs.mcp import JACSMCPServer
from fastmcp import FastMCP
import uvicorn

# Initialize JACS
agent = jacs.JacsAgent()
agent.load('./jacs.config.json')

# Create FastMCP server with JACS
mcp = JACSMCPServer(FastMCP("JACS Demo Server"))

@mcp.tool()
def echo(message: str) -> str:
    """Echo the input message"""
    return f"Echo: {message}"

@mcp.tool()
def calculate(operation: str, a: float, b: float) -> str:
    """Perform basic arithmetic"""
    if operation == 'add':
        result = a + b
    elif operation == 'subtract':
        result = a - b
    elif operation == 'multiply':
        result = a * b
    elif operation == 'divide':
        result = a / b if b != 0 else "undefined"
    else:
        return f"Unknown operation: {operation}"

    return f"{a} {operation} {b} = {result}"

@mcp.resource("info://server")
def server_info() -> str:
    """Get server information"""
    return json.dumps({
        "name": "JACS Demo Server",
        "version": "1.0.0",
        "tools": ["echo", "calculate"]
    })

# Get ASGI app with JACS middleware
app = mcp.sse_app()

if __name__ == "__main__":
    print("Starting JACS MCP Server...")
    uvicorn.run(app, host="localhost", port=8000)

MCP Client with JACS

import asyncio
import jacs
from jacs.mcp import JACSMCPClient

async def main():
    # Initialize JACS
    agent = jacs.JacsAgent()
    agent.load('./jacs.client.config.json')

    # Create authenticated client
    client = JACSMCPClient("http://localhost:8000/sse")

    async with client:
        # Call echo tool
        echo_result = await client.call_tool("echo", {
            "message": "Hello from JACS client!"
        })
        print(f"Echo: {echo_result}")

        # Call calculate tool
        calc_result = await client.call_tool("calculate", {
            "operation": "multiply",
            "a": 6,
            "b": 7
        })
        print(f"Calculate: {calc_result}")

        # Read resource
        info = await client.read_resource("info://server")
        print(f"Server info: {info}")

if __name__ == "__main__":
    asyncio.run(main())

Agreements

Creating Multi-Party Agreements

import jacs
import json

def create_agreement():
    # Initialize agent
    agent = jacs.JacsAgent()
    agent.load('./jacs.config.json')

    # Create contract document
    contract = {
        "type": "service_agreement",
        "title": "Professional Services Agreement",
        "parties": ["Company A", "Company B"],
        "terms": "Terms and conditions here...",
        "value": 50000,
        "effectiveDate": "2024-02-01"
    }

    signed_contract = agent.create_document(json.dumps(contract))

    # Define required signers (replace with actual UUIDs)
    agent_ids = [
        "agent1-uuid-here",
        "agent2-uuid-here"
    ]

    # Create agreement
    agreement_doc = agent.create_agreement(
        signed_contract,
        agent_ids,
        question="Do you agree to the terms of this service agreement?",
        context="This is a legally binding agreement"
    )

    doc = json.loads(agreement_doc)
    print("Agreement created")
    print(f"Document ID: {doc['jacsId']}")
    print(f"Required signatures: {len(doc.get('jacsAgreement', {}).get('agentIDs', []))}")

    # Save for signing
    with open('agreement-pending.json', 'w') as f:
        f.write(agreement_doc)

    return doc

if __name__ == "__main__":
    create_agreement()

Signing Agreements

import jacs
import json

def sign_agreement(agreement_path: str, output_path: str) -> dict:
    # Initialize agent
    agent = jacs.JacsAgent()
    agent.load('./jacs.config.json')

    # Read agreement
    with open(agreement_path, 'r') as f:
        agreement_doc = f.read()

    # Sign agreement
    signed_agreement = agent.sign_agreement(agreement_doc)

    # Check status
    status_json = agent.check_agreement(signed_agreement)
    status = json.loads(status_json)

    print("Agreement signed")
    print(f"Status: {'Complete' if status.get('complete') else 'Pending'}")
    print(f"Signatures: {len(status.get('signatures', []))}")

    # Save
    with open(output_path, 'w') as f:
        f.write(signed_agreement)

    return status

if __name__ == "__main__":
    sign_agreement('./agreement-pending.json', './agreement-signed.json')

Checking Agreement Status

import jacs
import json

def check_agreement_status(agreement_path: str) -> dict:
    # Initialize agent
    agent = jacs.JacsAgent()
    agent.load('./jacs.config.json')

    with open(agreement_path, 'r') as f:
        agreement_doc = f.read()

    status_json = agent.check_agreement(agreement_doc)
    status = json.loads(status_json)

    print("Agreement Status:")
    print(f"  Complete: {status.get('complete')}")
    print(f"  Required agents: {status.get('requiredAgents', [])}")
    print(f"  Signed by: {status.get('signedBy', [])}")
    print(f"  Missing: {status.get('missing', [])}")

    return status

if __name__ == "__main__":
    check_agreement_status('./agreement.json')

Document Store

Simple File-Based Store

import jacs
import json
import os
from pathlib import Path
from typing import Optional, Dict, List

class JacsDocumentStore:
    def __init__(self, config_path: str, data_dir: str = './documents'):
        self.config_path = config_path
        self.data_dir = Path(data_dir)
        self.agent = None

    def initialize(self):
        self.agent = jacs.JacsAgent()
        self.agent.load(self.config_path)
        self.data_dir.mkdir(parents=True, exist_ok=True)

    def create(self, content: dict) -> dict:
        signed_doc = self.agent.create_document(json.dumps(content))
        doc = json.loads(signed_doc)

        filename = f"{doc['jacsId']}.json"
        filepath = self.data_dir / filename

        with open(filepath, 'w') as f:
            f.write(signed_doc)

        return {
            'id': doc['jacsId'],
            'version': doc['jacsVersion'],
            'path': str(filepath)
        }

    def get(self, document_id: str) -> Optional[dict]:
        filepath = self.data_dir / f"{document_id}.json"

        if not filepath.exists():
            return None

        with open(filepath, 'r') as f:
            return json.load(f)

    def verify(self, document_id: str) -> dict:
        filepath = self.data_dir / f"{document_id}.json"

        if not filepath.exists():
            return {'valid': False, 'error': 'Document not found'}

        with open(filepath, 'r') as f:
            doc_string = f.read()

        is_valid = self.agent.verify_document(doc_string)
        return {'valid': is_valid, 'document': json.loads(doc_string)}

    def list(self) -> List[str]:
        return [
            f.stem for f in self.data_dir.glob('*.json')
        ]

if __name__ == "__main__":
    store = JacsDocumentStore('./jacs.config.json')
    store.initialize()

    # Create document
    result = store.create({
        'type': 'note',
        'title': 'Meeting Notes',
        'content': 'Discussed project timeline...'
    })
    print(f"Created: {result['id']}")

    # Verify document
    verification = store.verify(result['id'])
    print(f"Valid: {verification['valid']}")

    # List all documents
    docs = store.list()
    print(f"Documents: {docs}")

Batch Processing

Batch Document Creator

import jacs
import json
from pathlib import Path
from concurrent.futures import ThreadPoolExecutor

class BatchDocumentProcessor:
    def __init__(self, config_path: str):
        self.config_path = config_path

    def create_documents(self, documents: list, output_dir: str) -> list:
        """Create multiple signed documents"""
        output_path = Path(output_dir)
        output_path.mkdir(parents=True, exist_ok=True)

        results = []

        # Initialize agent
        agent = jacs.JacsAgent()
        agent.load(self.config_path)

        for i, content in enumerate(documents):
            try:
                signed_doc = agent.create_document(json.dumps(content))
                doc = json.loads(signed_doc)

                filename = f"{doc['jacsId']}.json"
                filepath = output_path / filename

                with open(filepath, 'w') as f:
                    f.write(signed_doc)

                results.append({
                    'success': True,
                    'index': i,
                    'id': doc['jacsId'],
                    'path': str(filepath)
                })
            except Exception as e:
                results.append({
                    'success': False,
                    'index': i,
                    'error': str(e)
                })

        return results

    def verify_documents(self, input_dir: str) -> list:
        """Verify all documents in a directory"""
        input_path = Path(input_dir)

        # Initialize agent
        agent = jacs.JacsAgent()
        agent.load(self.config_path)

        results = []

        for filepath in input_path.glob('*.json'):
            try:
                with open(filepath, 'r') as f:
                    doc_string = f.read()

                is_valid = agent.verify_document(doc_string)
                doc = json.loads(doc_string)

                results.append({
                    'file': filepath.name,
                    'valid': is_valid,
                    'id': doc.get('jacsId')
                })
            except Exception as e:
                results.append({
                    'file': filepath.name,
                    'valid': False,
                    'error': str(e)
                })

        return results

if __name__ == "__main__":
    processor = BatchDocumentProcessor('./jacs.config.json')

    # Create batch of documents
    documents = [
        {'type': 'invoice', 'number': f'INV-{i:03d}', 'amount': i * 100}
        for i in range(1, 11)
    ]

    results = processor.create_documents(documents, './batch-output')

    success_count = sum(1 for r in results if r['success'])
    print(f"Created {success_count}/{len(documents)} documents")

    # Verify all documents
    verification_results = processor.verify_documents('./batch-output')

    valid_count = sum(1 for r in verification_results if r['valid'])
    print(f"Valid: {valid_count}/{len(verification_results)} documents")

Testing

Pytest Setup

# tests/test_jacs.py
import pytest
import jacs
import json
import tempfile
import shutil
from pathlib import Path

@pytest.fixture
def jacs_agent():
    """Create a test JACS agent with temporary directories"""
    temp_dir = tempfile.mkdtemp()
    data_dir = Path(temp_dir) / 'data'
    key_dir = Path(temp_dir) / 'keys'

    data_dir.mkdir()
    key_dir.mkdir()

    config = {
        'jacs_data_directory': str(data_dir),
        'jacs_key_directory': str(key_dir),
        'jacs_agent_key_algorithm': 'ring-Ed25519',
        'jacs_default_storage': 'fs'
    }

    config_path = Path(temp_dir) / 'jacs.config.json'
    with open(config_path, 'w') as f:
        json.dump(config, f)

    agent = jacs.JacsAgent()
    agent.load(str(config_path))

    yield agent

    shutil.rmtree(temp_dir)

class TestDocumentOperations:
    def test_create_document(self, jacs_agent):
        content = {'title': 'Test Document', 'value': 42}
        signed_doc = jacs_agent.create_document(json.dumps(content))
        doc = json.loads(signed_doc)

        assert 'jacsId' in doc
        assert 'jacsVersion' in doc
        assert 'jacsSignature' in doc
        assert doc['title'] == 'Test Document'

    def test_verify_valid_document(self, jacs_agent):
        content = {'title': 'Verify Test'}
        signed_doc = jacs_agent.create_document(json.dumps(content))

        is_valid = jacs_agent.verify_document(signed_doc)
        assert is_valid is True

    def test_detect_tampered_document(self, jacs_agent):
        content = {'title': 'Tamper Test'}
        signed_doc = jacs_agent.create_document(json.dumps(content))

        # Tamper with document
        doc = json.loads(signed_doc)
        doc['title'] = 'Modified Title'
        tampered_doc = json.dumps(doc)

        is_valid = jacs_agent.verify_document(tampered_doc)
        assert is_valid is False

    def test_different_content_different_signatures(self, jacs_agent):
        doc1 = jacs_agent.create_document(json.dumps({'a': 1}))
        doc2 = jacs_agent.create_document(json.dumps({'a': 2}))

        parsed1 = json.loads(doc1)
        parsed2 = json.loads(doc2)

        sig1 = parsed1['jacsSignature']['signature']
        sig2 = parsed2['jacsSignature']['signature']

        assert sig1 != sig2

Error Handling

Robust Error Handling Pattern

import jacs
import json
from typing import Optional

class JacsError(Exception):
    def __init__(self, message: str, code: str, details: dict = None):
        super().__init__(message)
        self.code = code
        self.details = details or {}

def robust_create_document(config_path: str, content: dict) -> dict:
    """Create a document with comprehensive error handling"""
    try:
        agent = jacs.JacsAgent()
        agent.load(config_path)
    except FileNotFoundError:
        raise JacsError(
            "Configuration file not found",
            "CONFIG_NOT_FOUND",
            {"path": config_path}
        )
    except Exception as e:
        raise JacsError(
            "Failed to initialize JACS agent",
            "INIT_ERROR",
            {"original_error": str(e)}
        )

    try:
        signed_doc = agent.create_document(json.dumps(content))
        return json.loads(signed_doc)
    except Exception as e:
        raise JacsError(
            "Failed to create document",
            "CREATE_ERROR",
            {"original_error": str(e), "content": content}
        )

def robust_verify_document(config_path: str, doc_string: str) -> dict:
    """Verify a document with comprehensive error handling"""
    try:
        agent = jacs.JacsAgent()
        agent.load(config_path)
    except Exception as e:
        raise JacsError(
            "Failed to initialize JACS agent",
            "INIT_ERROR",
            {"original_error": str(e)}
        )

    try:
        is_valid = agent.verify_document(doc_string)
        return {"valid": is_valid}
    except Exception as e:
        raise JacsError(
            "Verification error",
            "VERIFY_ERROR",
            {"original_error": str(e)}
        )

if __name__ == "__main__":
    try:
        doc = robust_create_document('./jacs.config.json', {'title': 'Test'})
        print(f"Created: {doc['jacsId']}")
    except JacsError as e:
        print(f"JACS Error [{e.code}]: {e}")
        print(f"Details: {e.details}")
    except Exception as e:
        print(f"Unexpected error: {e}")

See Also

Integration Examples

This chapter provides complete, production-ready integration examples combining multiple JACS features.

Multi-Agent Contract Signing System

A complete example of a contract signing workflow with multiple agents.

Overview

┌──────────────┐     ┌──────────────┐     ┌──────────────┐
│   Seller     │     │   Contract   │     │   Buyer      │
│   Agent      │────>│   Document   │<────│   Agent      │
└──────────────┘     └──────────────┘     └──────────────┘
       │                    │                    │
       └────────────────────┼────────────────────┘
                           ↓
                   ┌──────────────┐
                   │   Signed     │
                   │   Agreement  │
                   └──────────────┘

Implementation

// contract-system.js
import { JacsAgent } from 'jacsnpm';
import fs from 'fs';

class ContractSigningSystem {
  constructor() {
    this.agents = new Map();
  }

  async registerAgent(name, configPath) {
    const agent = new JacsAgent();
    await agent.load(configPath);
    this.agents.set(name, { agent, configPath });
    return agent;
  }

  async createContract(content, sellerName) {
    const seller = this.agents.get(sellerName);
    if (!seller) throw new Error(`Agent ${sellerName} not found`);

    // Create and sign the contract
    const signedContract = await seller.agent.createDocument(
      JSON.stringify(content)
    );

    return JSON.parse(signedContract);
  }

  async createAgreement(contract, agentNames, question) {
    const firstAgent = this.agents.get(agentNames[0]);
    if (!firstAgent) throw new Error(`Agent ${agentNames[0]} not found`);

    // Get agent IDs
    const agentIds = [];
    for (const name of agentNames) {
      const agent = this.agents.get(name);
      if (!agent) throw new Error(`Agent ${name} not found`);
      // Get agent ID from config
      const config = JSON.parse(fs.readFileSync(agent.configPath, 'utf-8'));
      agentIds.push(config.jacs_agent_id_and_version.split(':')[0]);
    }

    const agreementDoc = await firstAgent.agent.createAgreement(
      JSON.stringify(contract),
      agentIds,
      question,
      'Legal contract requiring signatures from all parties'
    );

    return JSON.parse(agreementDoc);
  }

  async signAgreement(agreement, agentName) {
    const agent = this.agents.get(agentName);
    if (!agent) throw new Error(`Agent ${agentName} not found`);

    const signedDoc = await agent.agent.signAgreement(
      JSON.stringify(agreement)
    );

    return JSON.parse(signedDoc);
  }

  async checkAgreementStatus(agreement, agentName) {
    const agent = this.agents.get(agentName);
    if (!agent) throw new Error(`Agent ${agentName} not found`);

    const statusJson = await agent.agent.checkAgreement(
      JSON.stringify(agreement)
    );

    return JSON.parse(statusJson);
  }
}

// Usage
async function runContractWorkflow() {
  const system = new ContractSigningSystem();

  // Register agents
  await system.registerAgent('seller', './seller.config.json');
  await system.registerAgent('buyer', './buyer.config.json');

  // Create contract
  const contract = await system.createContract({
    type: 'purchase_agreement',
    parties: {
      seller: 'Widget Corp',
      buyer: 'Acme Inc'
    },
    items: [
      { name: 'Premium Widgets', quantity: 1000, unitPrice: 10.00 }
    ],
    totalValue: 10000,
    terms: 'Payment due within 30 days of delivery',
    effectiveDate: new Date().toISOString()
  }, 'seller');

  console.log('Contract created:', contract.jacsId);

  // Create agreement
  const agreement = await system.createAgreement(
    contract,
    ['seller', 'buyer'],
    'Do you agree to the terms of this purchase agreement?'
  );

  console.log('Agreement created, awaiting signatures');

  // Seller signs
  let signedAgreement = await system.signAgreement(agreement, 'seller');
  console.log('Seller signed');

  // Check status
  let status = await system.checkAgreementStatus(signedAgreement, 'seller');
  console.log('Status after seller:', status.complete ? 'Complete' : 'Pending');

  // Buyer signs
  signedAgreement = await system.signAgreement(signedAgreement, 'buyer');
  console.log('Buyer signed');

  // Final status
  status = await system.checkAgreementStatus(signedAgreement, 'buyer');
  console.log('Final status:', status.complete ? 'Complete' : 'Pending');

  // Save completed agreement
  fs.writeFileSync(
    './completed-agreement.json',
    JSON.stringify(signedAgreement, null, 2)
  );

  return signedAgreement;
}

runContractWorkflow().catch(console.error);

Secure API Gateway with MCP Tools

A complete API gateway that authenticates requests and provides MCP tools.

Node.js Implementation

// api-gateway.js
import express from 'express';
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
import { JACSExpressMiddleware } from 'jacsnpm/http';
import { createJACSTransportProxy } from 'jacsnpm/mcp';
import { JacsAgent } from 'jacsnpm';
import { z } from 'zod';

// Initialize Express
const app = express();
const PORT = 3000;

// Initialize JACS
const agent = new JacsAgent();
await agent.load('./jacs.config.json');

// Create MCP server with tools
const mcpServer = new McpServer({
  name: "secure-api-gateway",
  version: "1.0.0"
});

// Document operations tool
mcpServer.tool("create_document", {
  content: z.object({}).passthrough().describe("Document content"),
  type: z.string().optional().describe("Document type")
}, async ({ content, type }) => {
  const doc = await agent.createDocument(JSON.stringify({
    ...content,
    documentType: type || 'generic'
  }));
  const parsed = JSON.parse(doc);
  return {
    content: [{
      type: "text",
      text: JSON.stringify({
        success: true,
        documentId: parsed.jacsId,
        version: parsed.jacsVersion
      })
    }]
  };
});

mcpServer.tool("verify_document", {
  document: z.string().describe("JSON document string to verify")
}, async ({ document }) => {
  try {
    const isValid = await agent.verifyDocument(document);
    return {
      content: [{
        type: "text",
        text: JSON.stringify({ valid: isValid })
      }]
    };
  } catch (error) {
    return {
      content: [{
        type: "text",
        text: JSON.stringify({ valid: false, error: error.message })
      }]
    };
  }
});

// Health check (unauthenticated)
app.get('/health', (req, res) => {
  res.json({ status: 'healthy', timestamp: new Date().toISOString() });
});

// REST API routes (JACS authenticated)
app.use('/api', express.text({ type: '*/*' }));
app.use('/api', JACSExpressMiddleware({
  configPath: './jacs.config.json'
}));

// Document REST endpoint
app.post('/api/documents', async (req, res) => {
  if (!req.jacsPayload) {
    return res.status(400).send({ error: 'Invalid JACS request' });
  }

  try {
    const signedDoc = await agent.createDocument(
      JSON.stringify(req.jacsPayload)
    );
    const doc = JSON.parse(signedDoc);

    res.send({
      success: true,
      documentId: doc.jacsId,
      version: doc.jacsVersion
    });
  } catch (error) {
    res.status(500).send({ error: error.message });
  }
});

app.post('/api/documents/verify', async (req, res) => {
  if (!req.jacsPayload) {
    return res.status(400).send({ error: 'Invalid JACS request' });
  }

  try {
    const isValid = await agent.verifyDocument(
      JSON.stringify(req.jacsPayload.document)
    );
    res.send({ valid: isValid });
  } catch (error) {
    res.status(500).send({ error: error.message });
  }
});

// MCP SSE endpoint (JACS authenticated)
const activeSessions = new Map();

app.get('/mcp/sse', async (req, res) => {
  const sessionId = Date.now().toString();

  // Create SSE transport with JACS
  const baseTransport = new SSEServerTransport('/mcp/messages', res);
  const secureTransport = createJACSTransportProxy(
    baseTransport,
    './jacs.config.json',
    'server'
  );

  activeSessions.set(sessionId, { transport: secureTransport, res });

  // Connect MCP server
  await mcpServer.connect(secureTransport);

  res.on('close', () => {
    activeSessions.delete(sessionId);
  });
});

app.post('/mcp/messages', express.text({ type: '*/*' }), async (req, res) => {
  // Find the active session and handle the message
  for (const [id, session] of activeSessions) {
    try {
      await session.transport.handlePostMessage(req, res, req.body);
      return;
    } catch (error) {
      // Try next session
    }
  }
  res.status(404).send({ error: 'No active session' });
});

// Start server
app.listen(PORT, () => {
  console.log(`Secure API Gateway running on port ${PORT}`);
  console.log(`  REST API: http://localhost:${PORT}/api`);
  console.log(`  MCP SSE:  http://localhost:${PORT}/mcp/sse`);
});

Python Implementation

# api_gateway.py
from fastapi import FastAPI, Request, HTTPException
from fastapi.responses import PlainTextResponse
import jacs
from jacs.mcp import JACSMCPServer
from fastmcp import FastMCP
import uvicorn
import json

app = FastAPI(title="Secure API Gateway")

# Initialize JACS
agent = jacs.JacsAgent()
agent.load('./jacs.config.json')

# Create MCP server with JACS
mcp = JACSMCPServer(FastMCP("secure-api-gateway"))

@mcp.tool()
def create_document(content: dict, document_type: str = "generic") -> str:
    """Create a signed JACS document"""
    doc_content = {**content, "documentType": document_type}
    signed_doc = agent.create_document(json.dumps(doc_content))
    parsed = json.loads(signed_doc)
    return json.dumps({
        "success": True,
        "documentId": parsed["jacsId"],
        "version": parsed["jacsVersion"]
    })

@mcp.tool()
def verify_document(document: str) -> str:
    """Verify a JACS document signature"""
    try:
        is_valid = agent.verify_document(document)
        return json.dumps({"valid": is_valid})
    except Exception as e:
        return json.dumps({"valid": False, "error": str(e)})

# Health check
@app.get("/health")
async def health():
    return {"status": "healthy"}

# REST API endpoints
@app.post("/api/documents")
async def create_doc(request: Request):
    body = await request.body()
    body_str = body.decode('utf-8')

    try:
        verified = jacs.verify_request(body_str)
        payload = json.loads(verified).get('payload')
    except Exception as e:
        raise HTTPException(status_code=400, detail="Invalid JACS request")

    signed_doc = agent.create_document(json.dumps(payload))
    doc = json.loads(signed_doc)

    result = {
        "success": True,
        "documentId": doc["jacsId"],
        "version": doc["jacsVersion"]
    }

    signed_response = jacs.sign_response(result)
    return PlainTextResponse(content=signed_response)

@app.post("/api/documents/verify")
async def verify_doc(request: Request):
    body = await request.body()
    body_str = body.decode('utf-8')

    try:
        verified = jacs.verify_request(body_str)
        payload = json.loads(verified).get('payload')
    except Exception as e:
        raise HTTPException(status_code=400, detail="Invalid JACS request")

    document = payload.get("document")
    is_valid = agent.verify_document(json.dumps(document))

    result = {"valid": is_valid}
    signed_response = jacs.sign_response(result)
    return PlainTextResponse(content=signed_response)

# Mount MCP SSE endpoint
app.mount("/mcp", mcp.sse_app())

if __name__ == "__main__":
    print("Secure API Gateway running")
    print("  REST API: http://localhost:8000/api")
    print("  MCP SSE:  http://localhost:8000/mcp/sse")
    uvicorn.run(app, host="localhost", port=8000)

Document Audit Trail System

Track and verify document history with cryptographic proofs.

// audit-trail.js
import { JacsAgent } from 'jacsnpm';
import fs from 'fs';
import path from 'path';

class AuditTrailSystem {
  constructor(configPath, auditDir = './audit') {
    this.configPath = configPath;
    this.auditDir = auditDir;
    this.agent = null;
  }

  async initialize() {
    this.agent = new JacsAgent();
    await this.agent.load(this.configPath);

    if (!fs.existsSync(this.auditDir)) {
      fs.mkdirSync(this.auditDir, { recursive: true });
    }
  }

  async createDocument(content, metadata = {}) {
    const auditEntry = {
      action: 'create',
      timestamp: new Date().toISOString(),
      content,
      metadata
    };

    const signedDoc = await this.agent.createDocument(
      JSON.stringify({ ...content, _audit: auditEntry })
    );

    const doc = JSON.parse(signedDoc);

    // Save to audit log
    await this.logAuditEntry(doc.jacsId, 'create', doc);

    return doc;
  }

  async updateDocument(originalDoc, newContent, metadata = {}) {
    const auditEntry = {
      action: 'update',
      timestamp: new Date().toISOString(),
      previousVersion: originalDoc.jacsVersion,
      changes: this.computeChanges(originalDoc, newContent),
      metadata
    };

    const updatedDoc = await this.agent.updateDocument(
      JSON.stringify(originalDoc),
      JSON.stringify({ ...newContent, _audit: auditEntry })
    );

    const doc = JSON.parse(updatedDoc);

    // Save to audit log
    await this.logAuditEntry(doc.jacsId, 'update', doc);

    return doc;
  }

  computeChanges(original, updated) {
    const changes = [];

    for (const [key, value] of Object.entries(updated)) {
      if (key.startsWith('_')) continue;

      if (!(key in original)) {
        changes.push({ field: key, type: 'added', newValue: value });
      } else if (JSON.stringify(original[key]) !== JSON.stringify(value)) {
        changes.push({
          field: key,
          type: 'modified',
          oldValue: original[key],
          newValue: value
        });
      }
    }

    for (const key of Object.keys(original)) {
      if (key.startsWith('_') || key.startsWith('jacs')) continue;
      if (!(key in updated)) {
        changes.push({ field: key, type: 'removed', oldValue: original[key] });
      }
    }

    return changes;
  }

  async logAuditEntry(documentId, action, document) {
    const logFile = path.join(this.auditDir, `${documentId}.audit.jsonl`);

    const entry = {
      timestamp: new Date().toISOString(),
      action,
      documentId,
      version: document.jacsVersion,
      signature: document.jacsSignature,
      hash: document.jacsSha256
    };

    fs.appendFileSync(logFile, JSON.stringify(entry) + '\n');
  }

  async getAuditTrail(documentId) {
    const logFile = path.join(this.auditDir, `${documentId}.audit.jsonl`);

    if (!fs.existsSync(logFile)) {
      return [];
    }

    const lines = fs.readFileSync(logFile, 'utf-8').trim().split('\n');
    return lines.map(line => JSON.parse(line));
  }

  async verifyAuditTrail(documentId) {
    const trail = await this.getAuditTrail(documentId);
    const results = [];

    for (const entry of trail) {
      // Load and verify each version
      const docPath = path.join(
        this.auditDir,
        'documents',
        documentId,
        `${entry.version}.json`
      );

      if (fs.existsSync(docPath)) {
        const docString = fs.readFileSync(docPath, 'utf-8');
        const isValid = await this.agent.verifyDocument(docString);

        results.push({
          version: entry.version,
          timestamp: entry.timestamp,
          action: entry.action,
          valid: isValid
        });
      } else {
        results.push({
          version: entry.version,
          timestamp: entry.timestamp,
          action: entry.action,
          valid: null,
          error: 'Document file not found'
        });
      }
    }

    return results;
  }
}

// Usage
async function runAuditExample() {
  const audit = new AuditTrailSystem('./jacs.config.json');
  await audit.initialize();

  // Create a document
  const doc = await audit.createDocument({
    type: 'financial_report',
    period: 'Q1 2024',
    revenue: 1000000,
    expenses: 750000
  }, { author: 'Finance Team' });

  console.log('Created document:', doc.jacsId);

  // Update the document
  const updated = await audit.updateDocument(doc, {
    type: 'financial_report',
    period: 'Q1 2024',
    revenue: 1000000,
    expenses: 750000,
    profit: 250000,  // Added field
    status: 'approved'
  }, { author: 'CFO', reason: 'Added profit calculation' });

  console.log('Updated to version:', updated.jacsVersion);

  // Get audit trail
  const trail = await audit.getAuditTrail(doc.jacsId);
  console.log('Audit trail:');
  for (const entry of trail) {
    console.log(`  ${entry.timestamp} - ${entry.action} (v${entry.version})`);
  }
}

runAuditExample().catch(console.error);

Multi-Tenant Document Service

A complete multi-tenant document service with isolated agents per tenant.

# multi_tenant.py
import jacs
import json
import os
from pathlib import Path
from typing import Dict, Optional

class TenantManager:
    def __init__(self, base_dir: str = './tenants'):
        self.base_dir = Path(base_dir)
        self.agents: Dict[str, jacs.JacsAgent] = {}

    def initialize_tenant(self, tenant_id: str) -> dict:
        """Create a new tenant with its own JACS agent"""
        tenant_dir = self.base_dir / tenant_id
        data_dir = tenant_dir / 'data'
        key_dir = tenant_dir / 'keys'

        # Create directories
        data_dir.mkdir(parents=True, exist_ok=True)
        key_dir.mkdir(parents=True, exist_ok=True)

        # Create tenant config
        config = {
            "jacs_data_directory": str(data_dir),
            "jacs_key_directory": str(key_dir),
            "jacs_agent_key_algorithm": "ring-Ed25519",
            "jacs_default_storage": "fs"
        }

        config_path = tenant_dir / 'jacs.config.json'
        with open(config_path, 'w') as f:
            json.dump(config, f, indent=2)

        # Initialize agent
        agent = jacs.JacsAgent()
        agent.load(str(config_path))
        self.agents[tenant_id] = agent

        return {
            "tenant_id": tenant_id,
            "config_path": str(config_path),
            "initialized": True
        }

    def get_agent(self, tenant_id: str) -> Optional[jacs.JacsAgent]:
        """Get the JACS agent for a tenant"""
        if tenant_id not in self.agents:
            # Try to load existing tenant
            config_path = self.base_dir / tenant_id / 'jacs.config.json'
            if config_path.exists():
                agent = jacs.JacsAgent()
                agent.load(str(config_path))
                self.agents[tenant_id] = agent

        return self.agents.get(tenant_id)

    def create_document(self, tenant_id: str, content: dict) -> dict:
        """Create a document for a tenant"""
        agent = self.get_agent(tenant_id)
        if not agent:
            raise ValueError(f"Tenant {tenant_id} not found")

        signed_doc = agent.create_document(json.dumps(content))
        return json.loads(signed_doc)

    def verify_document(self, tenant_id: str, doc_string: str) -> bool:
        """Verify a document for a tenant"""
        agent = self.get_agent(tenant_id)
        if not agent:
            raise ValueError(f"Tenant {tenant_id} not found")

        return agent.verify_document(doc_string)

    def list_tenants(self) -> list:
        """List all tenants"""
        if not self.base_dir.exists():
            return []

        return [
            d.name for d in self.base_dir.iterdir()
            if d.is_dir() and (d / 'jacs.config.json').exists()
        ]

class MultiTenantDocumentService:
    def __init__(self):
        self.tenant_manager = TenantManager()

    def create_tenant(self, tenant_id: str) -> dict:
        return self.tenant_manager.initialize_tenant(tenant_id)

    def create_document(self, tenant_id: str, content: dict) -> dict:
        doc = self.tenant_manager.create_document(tenant_id, content)

        # Save document
        tenant_dir = self.tenant_manager.base_dir / tenant_id / 'documents'
        tenant_dir.mkdir(parents=True, exist_ok=True)

        doc_path = tenant_dir / f"{doc['jacsId']}.json"
        with open(doc_path, 'w') as f:
            json.dump(doc, f, indent=2)

        return {
            "tenant_id": tenant_id,
            "document_id": doc['jacsId'],
            "version": doc['jacsVersion'],
            "path": str(doc_path)
        }

    def get_document(self, tenant_id: str, document_id: str) -> Optional[dict]:
        doc_path = (
            self.tenant_manager.base_dir / tenant_id /
            'documents' / f"{document_id}.json"
        )

        if not doc_path.exists():
            return None

        with open(doc_path, 'r') as f:
            return json.load(f)

    def verify_document(self, tenant_id: str, document_id: str) -> dict:
        doc = self.get_document(tenant_id, document_id)
        if not doc:
            return {"valid": False, "error": "Document not found"}

        is_valid = self.tenant_manager.verify_document(
            tenant_id, json.dumps(doc)
        )

        return {
            "tenant_id": tenant_id,
            "document_id": document_id,
            "valid": is_valid
        }

# Usage
if __name__ == "__main__":
    service = MultiTenantDocumentService()

    # Create tenants
    tenant1 = service.create_tenant("acme-corp")
    tenant2 = service.create_tenant("globex-inc")
    print(f"Created tenants: {tenant1['tenant_id']}, {tenant2['tenant_id']}")

    # Create documents for each tenant
    doc1 = service.create_document("acme-corp", {
        "type": "invoice",
        "amount": 5000,
        "customer": "John Doe"
    })
    print(f"Acme Corp document: {doc1['document_id']}")

    doc2 = service.create_document("globex-inc", {
        "type": "contract",
        "value": 100000,
        "parties": ["Globex", "Initech"]
    })
    print(f"Globex Inc document: {doc2['document_id']}")

    # Verify documents
    verify1 = service.verify_document("acme-corp", doc1['document_id'])
    verify2 = service.verify_document("globex-inc", doc2['document_id'])
    print(f"Acme Corp verification: {verify1['valid']}")
    print(f"Globex Inc verification: {verify2['valid']}")

    # Cross-tenant verification should fail
    cross = service.verify_document("acme-corp", doc2['document_id'])
    print(f"Cross-tenant verification: {cross}")

Webhook Notification System

Notify external systems when documents are signed.

// webhook-notifier.js
import { JacsAgent } from 'jacsnpm';
import express from 'express';
import { JACSExpressMiddleware } from 'jacsnpm/http';

const app = express();
const PORT = 3000;

// Initialize JACS
const agent = new JacsAgent();
await agent.load('./jacs.config.json');

// Webhook configuration
const webhooks = new Map();

// Register a webhook
app.post('/webhooks', express.json(), (req, res) => {
  const { url, events, secret } = req.body;
  const webhookId = crypto.randomUUID();

  webhooks.set(webhookId, { url, events, secret, active: true });

  res.json({ webhookId, message: 'Webhook registered' });
});

// JACS-protected document endpoints
app.use('/api', express.text({ type: '*/*' }));
app.use('/api', JACSExpressMiddleware({
  configPath: './jacs.config.json'
}));

app.post('/api/documents', async (req, res) => {
  if (!req.jacsPayload) {
    return res.status(400).send({ error: 'Invalid JACS request' });
  }

  // Create document
  const signedDoc = await agent.createDocument(
    JSON.stringify(req.jacsPayload)
  );
  const doc = JSON.parse(signedDoc);

  // Notify webhooks
  await notifyWebhooks('document.created', {
    documentId: doc.jacsId,
    version: doc.jacsVersion,
    timestamp: new Date().toISOString()
  });

  res.send({
    success: true,
    documentId: doc.jacsId
  });
});

async function notifyWebhooks(event, payload) {
  for (const [id, webhook] of webhooks) {
    if (!webhook.active) continue;
    if (!webhook.events.includes(event) && !webhook.events.includes('*')) continue;

    try {
      // Sign the webhook payload with JACS
      const signedPayload = await agent.signRequest({
        event,
        payload,
        timestamp: new Date().toISOString(),
        webhookId: id
      });

      // Send webhook
      const response = await fetch(webhook.url, {
        method: 'POST',
        headers: {
          'Content-Type': 'text/plain',
          'X-JACS-Signature': 'v1',
          'X-Webhook-Secret': webhook.secret
        },
        body: signedPayload
      });

      if (!response.ok) {
        console.error(`Webhook ${id} failed: ${response.status}`);
      }
    } catch (error) {
      console.error(`Webhook ${id} error:`, error.message);
    }
  }
}

app.listen(PORT, () => {
  console.log(`Webhook notification server running on port ${PORT}`);
});

See Also

CLI Command Reference

This page provides a comprehensive reference for all JACS command-line interface commands.

Global Commands

jacs version

Prints version and build information for the JACS installation.

jacs version

jacs init

Initialize JACS by creating both configuration and agent (with cryptographic keys). This is typically the first command run when setting up JACS.

jacs init

jacs help

Print help information for JACS commands.

jacs help [COMMAND]

Configuration Commands

jacs config

Work with JACS configuration settings.

jacs config [SUBCOMMAND]

Note: Specific subcommands for config are not detailed in the current help output.

Agent Commands

jacs agent

Work with JACS agents - the cryptographic identities that sign and verify documents.

jacs agent [SUBCOMMAND]

Note: Specific subcommands for agent management are not detailed in the current help output.

Task Commands

jacs task

Work with JACS agent tasks - structured workflows between agents.

jacs task [SUBCOMMAND]

Note: Specific subcommands for task management are not detailed in the current help output.

Document Commands

The jacs document command provides comprehensive document management capabilities.

jacs document create

Create a new JACS document, either by embedding or parsing a document with optional file attachments.

Usage:

jacs document create [OPTIONS]

Options:

  • -a <agent-file> - Path to the agent file. If not specified, uses config jacs_agent_id_and_version
  • -f <filename> - Path to input file. Must be JSON format
  • -o <output> - Output filename for the created document
  • -d <directory> - Path to directory of files. Files should end with .json
  • -v, --verbose - Enable verbose output
  • -n, --no-save - Instead of saving files, print to stdout
  • -s, --schema <schema> - Path to JSON schema file to use for validation
  • --attach <attach> - Path to file or directory for file attachments
  • -e, --embed <embed> - Embed documents or keep them external [possible values: true, false]
  • -h, --help - Print help information

Examples:

# Create document from JSON file
jacs document create -f my-document.json

# Create document with embedded attachment
jacs document create -f document.json --attach ./image.jpg --embed true

# Create document with referenced attachment
jacs document create -f document.json --attach ./data.csv --embed false

# Create from directory of JSON files
jacs document create -d ./documents/

# Create with custom schema validation
jacs document create -f document.json -s custom-schema.json

# Print to stdout instead of saving
jacs document create -f document.json --no-save

jacs document update

Create a new version of an existing document. Requires both the original JACS file and the modified JACS metadata.

Usage:

jacs document update [OPTIONS]

Options:

  • -a <agent-file> - Path to the agent file
  • -f <filename> - Path to original document file
  • -n <new-file> - Path to new/modified document file
  • -o <output> - Output filename for updated document
  • -v, --verbose - Enable verbose output
  • -n, --no-save - Print to stdout instead of saving
  • -s, --schema <schema> - Path to JSON schema file for validation
  • --attach <attach> - Path to file or directory for additional attachments
  • -e, --embed <embed> - Embed new attachments or keep them external
  • -h, --help - Print help information

Example:

# Update document with new version
jacs document update -f original.json -n modified.json -o updated.json

# Update and add new attachments
jacs document update -f original.json -n modified.json --attach ./new-file.pdf --embed false

jacs document verify

Verify a document's hash, signatures, and schema compliance.

Usage:

jacs document verify [OPTIONS]

Options:

  • -a <agent-file> - Path to the agent file
  • -f <filename> - Path to input file. Must be JSON format
  • -d <directory> - Path to directory of files. Files should end with .json
  • -v, --verbose - Enable verbose output
  • -s, --schema <schema> - Path to JSON schema file to use for validation
  • -h, --help - Print help information

Examples:

# Verify single document
jacs document verify -f signed-document.json

# Verify all documents in directory
jacs document verify -d ./documents/

# Verify with custom schema
jacs document verify -f document.json -s custom-schema.json

Verification Process:

  1. Hash verification - Confirms document integrity
  2. Signature verification - Validates cryptographic signatures
  3. Schema validation - Ensures document structure compliance
  4. File integrity - Checks SHA256 checksums of attached files

jacs document extract

Extract embedded file contents from documents back to the filesystem.

Usage:

jacs document extract [OPTIONS]

Options:

  • -a <agent-file> - Path to the agent file
  • -f <filename> - Path to input file containing embedded files
  • -d <directory> - Path to directory of files to process
  • -s, --schema <schema> - Path to JSON schema file for validation
  • -h, --help - Print help information

Examples:

# Extract embedded files from single document
jacs document extract -f document-with-embedded-files.json

# Extract from all documents in directory  
jacs document extract -d ./documents/

Extract Process:

  1. Reads embedded file contents from document
  2. Decodes base64-encoded data
  3. Writes files to their original paths
  4. Creates backup of existing files (with timestamp)

Agreement Commands

JACS provides specialized commands for managing multi-agent agreements.

jacs document check-agreement

Given a document, provide a list of agents that should sign the document.

Usage:

jacs document check-agreement [OPTIONS]

jacs document create-agreement

Create an agreement structure for a document that requires multiple agent signatures.

Usage:

jacs document create-agreement [OPTIONS]

jacs document sign-agreement

Sign the agreement section of a document with the current agent's cryptographic signature.

Usage:

jacs document sign-agreement [OPTIONS]

Common Patterns

Basic Document Lifecycle

# 1. Initialize JACS
jacs init

# 2. Create document with attachments
jacs document create -f document.json --attach ./files/ --embed true

# 3. Verify document integrity
jacs document verify -f created-document.json

# 4. Update document if needed
jacs document update -f original.json -n modified.json

# 5. Extract embedded files when needed
jacs document extract -f document.json

Working with Attachments

# Embed small files for portability
jacs document create -f doc.json --attach ./small-image.png --embed true

# Reference large files to save space
jacs document create -f doc.json --attach ./large-video.mp4 --embed false

# Attach multiple files from directory
jacs document create -f doc.json --attach ./attachments/ --embed false

Schema Validation Workflow

# Create with schema validation
jacs document create -f document.json -s schema.json

# Verify against specific schema
jacs document verify -f document.json -s schema.json

Global Options

Most commands support these common options:

  • -h, --help - Show help information
  • -v, --verbose - Enable verbose output for debugging
  • -a <agent-file> - Specify custom agent file (overrides config default)

Exit Codes

  • 0 - Success
  • 1 - General error (invalid arguments, file not found, etc.)
  • 2 - Verification failure (hash mismatch, invalid signature, etc.)
  • 3 - Schema validation failure

Environment Variables

  • JACS_CONFIG_PATH - Override default configuration file location
  • JACS_DATA_DIR - Override default data directory location
  • JACS_AGENT_FILE - Default agent file to use (if not specified with -a)

File Formats

Input Files

  • JSON documents - Must be valid JSON format
  • Schema files - JSON Schema format (draft-07 compatible)
  • Agent files - JACS agent format with cryptographic keys
  • Attachments - Any file type (automatically detected MIME type)

Output Files

  • JACS documents - JSON format with JACS metadata, signatures, and checksums
  • Extracted files - Original format of embedded attachments

Configuration Reference

Overview

Complete Example Configuration

{
  "$schema": "https://hai.ai/schemas/jacs.config.schema.json",
  "jacs_use_security": "false",
  "jacs_data_directory": "./jacs_data",
  "jacs_key_directory": "./jacs_keys",
  "jacs_agent_private_key_filename": "jacs.private.pem.enc",
  "jacs_agent_public_key_filename": "jacs.public.pem",
  "jacs_agent_key_algorithm": "RSA-PSS",
  "jacs_default_storage": "fs",
  "observability": {
    "logs": {
      "enabled": true,
      "level": "info",
      "destination": {
        "type": "file",
        "path": "./logs"
      },
      "headers": {
        "Authorization": "Bearer token",
        "X-API-Key": "secret"
      }
    },
    "metrics": {
      "enabled": true,
      "destination": {
        "type": "prometheus",
        "endpoint": "http://localhost:9090/api/v1/write",
        "headers": {
          "Authorization": "Basic dXNlcjpwYXNz"
        }
      },
      "export_interval_seconds": 60,
      "headers": {
        "X-Service": "jacs"
      }
    },
    "tracing": {
      "enabled": true,
      "sampling": {
        "ratio": 0.1,
        "parent_based": true,
        "rate_limit": 100
      },
      "resource": {
        "service_name": "jacs",
        "service_version": "0.4.0",
        "environment": "production",
        "attributes": {
          "team": "platform",
          "region": "us-west-2"
        }
      }
    }
  }
}

Observability Configuration

JACS supports comprehensive observability through configurable logging, metrics, and tracing. All observability features are optional and can be configured in the jacs.config.json file.

Logs Configuration

Controls how JACS generates and outputs log messages.

FieldTypeRequiredDescription
enabledbooleanYesWhether logging is enabled
levelstringYesMinimum log level: trace, debug, info, warn, error
destinationobjectYesWhere logs are sent (see destinations below)
headersobjectNoAdditional headers for remote destinations

Log Destinations

File Logging

{
  "type": "file",
  "path": "./logs"
}

Writes logs to rotating files in the specified directory.

Console Logging (stderr)

{
  "type": "stderr"
}

Outputs logs to standard error stream.

OpenTelemetry Protocol (OTLP)

{
  "type": "otlp",
  "endpoint": "http://localhost:4317",
  "headers": {
    "Authorization": "Bearer token"
  }
}

Sends logs to an OTLP-compatible endpoint (like Jaeger, Grafana Cloud).

Null (disabled)

{
  "type": "null"
}

Discards all log output.

Metrics Configuration

Controls collection and export of application metrics.

FieldTypeRequiredDescription
enabledbooleanYesWhether metrics collection is enabled
destinationobjectYesWhere metrics are exported (see destinations below)
export_interval_secondsintegerNoHow often to export metrics (default: 60)
headersobjectNoAdditional headers for remote destinations

Metrics Destinations

Prometheus Remote Write

{
  "type": "prometheus",
  "endpoint": "http://localhost:9090/api/v1/write",
  "headers": {
    "Authorization": "Basic dXNlcjpwYXNz"
  }
}

Exports metrics in Prometheus format to a remote write endpoint.

OpenTelemetry Protocol (OTLP)

{
  "type": "otlp",
  "endpoint": "http://localhost:4317",
  "headers": {
    "Authorization": "Bearer token"
  }
}

Exports metrics to an OTLP-compatible endpoint.

File Export

{
  "type": "file",
  "path": "./metrics.txt"
}

Writes metrics to a local file.

Console Output (stdout)

{
  "type": "stdout"
}

Prints metrics to standard output.

Tracing Configuration

Controls distributed tracing for request flows.

FieldTypeRequiredDescription
enabledbooleanYesWhether tracing is enabled
samplingobjectNoSampling configuration (see below)
resourceobjectNoService identification (see below)

Sampling Configuration

Controls which traces are collected to manage overhead.

FieldTypeDefaultDescription
rationumber1.0Fraction of traces to sample (0.0-1.0)
parent_basedbooleantrueWhether to respect parent trace sampling decisions
rate_limitintegernoneMaximum traces per second

Examples:

  • "ratio": 1.0 - Sample all traces (100%)
  • "ratio": 0.1 - Sample 10% of traces
  • "ratio": 0.01 - Sample 1% of traces
  • "rate_limit": 10 - Maximum 10 traces per second

Resource Configuration

Identifies the service in distributed tracing systems.

FieldTypeRequiredDescription
service_namestringYesName of the service
service_versionstringNoVersion of the service
environmentstringNoEnvironment (dev, staging, prod)
attributesobjectNoCustom key-value attributes

Authentication & Headers

For remote destinations (OTLP, Prometheus), you can specify authentication headers:

Bearer Token Authentication:

"headers": {
  "Authorization": "Bearer your-token-here"
}

Basic Authentication:

"headers": {
  "Authorization": "Basic dXNlcjpwYXNz"
}

API Key Authentication:

"headers": {
  "X-API-Key": "your-api-key",
  "X-Auth-Token": "your-auth-token"
}

Common Patterns

Development Configuration

"observability": {
  "logs": {
    "enabled": true,
    "level": "debug",
    "destination": { "type": "stderr" }
  },
  "metrics": {
    "enabled": true,
    "destination": { "type": "stdout" }
  }
}

Production Configuration

"observability": {
  "logs": {
    "enabled": true,
    "level": "info",
    "destination": {
      "type": "otlp",
      "endpoint": "https://logs.example.com:4317",
      "headers": {
        "Authorization": "Bearer prod-token"
      }
    }
  },
  "metrics": {
    "enabled": true,
    "destination": {
      "type": "prometheus",
      "endpoint": "https://metrics.example.com/api/v1/write"
    },
    "export_interval_seconds": 30
  },
  "tracing": {
    "enabled": true,
    "sampling": {
      "ratio": 0.05,
      "rate_limit": 100
    },
    "resource": {
      "service_name": "jacs",
      "service_version": "0.4.0",
      "environment": "production"
    }
  }
}

File-based Configuration

"observability": {
  "logs": {
    "enabled": true,
    "level": "info",
    "destination": {
      "type": "file",
      "path": "/var/log/jacs"
    }
  },
  "metrics": {
    "enabled": true,
    "destination": {
      "type": "file",
      "path": "/var/log/jacs/metrics.txt"
    },
    "export_interval_seconds": 60
  }
}

Environment Variable Integration

The observability configuration works alongside JACS's core configuration system.

Required Environment Variable

Only one environment variable is truly required:

  • JACS_PRIVATE_KEY_PASSWORD - Password for encrypting/decrypting private keys (required for cryptographic operations)

Configuration-Based Settings

All other JACS settings are configuration file fields that have sensible defaults:

  • jacs_data_directory - Where agent/document data is stored (default: ./jacs_data)
  • jacs_key_directory - Where cryptographic keys are stored (default: ./jacs_keys)
  • jacs_agent_key_algorithm - Cryptographic algorithm to use (default: RSA-PSS)
  • jacs_default_storage - Storage backend (default: fs)
  • jacs_use_security - Enable security features (default: false)

These can be overridden by environment variables if needed, but they are primarily configured through the jacs.config.json file.

The observability configuration is completely optional - JACS will work without any observability configuration.

Storage Configuration

The jacs_default_storage field determines where JACS stores agent data, documents, and keys. This is a critical configuration that affects how your data is persisted and accessed.

Available Storage Backends

BackendValueDescriptionUse Case
Filesystem"fs"Local file system storageDevelopment, single-node deployments
AWS S3"aws"Amazon S3 object storageProduction, cloud deployments
HAI Remote"hai"HAI.ai remote storage serviceHAI.ai platform integration
Memory"memory"In-memory storage (non-persistent)Testing, temporary data
Web Local"local"Browser local storage (WASM only)Web applications

Backend-Specific Configuration

Filesystem Storage ("fs")

{
  "jacs_default_storage": "fs",
  "jacs_data_directory": "./jacs_data",
  "jacs_key_directory": "./jacs_keys"
}

Requirements: None - works out of the box Data location: Local directories as specified in config Best for: Development, local testing, single-machine deployments

AWS S3 Storage ("aws")

{
  "jacs_default_storage": "aws"
}

Required Environment Variables:

  • JACS_ENABLE_AWS_BUCKET_NAME - S3 bucket name
  • AWS_ACCESS_KEY_ID - AWS access key
  • AWS_SECRET_ACCESS_KEY - AWS secret key
  • AWS_REGION - AWS region (optional, defaults to us-east-1)

Best for: Production deployments, distributed systems, cloud-native applications

HAI Remote Storage ("hai")

{
  "jacs_default_storage": "hai"
}

Required Environment Variables:

  • HAI_STORAGE_URL - HAI.ai storage service endpoint

Best for: Integration with HAI.ai platform services

Memory Storage ("memory")

{
  "jacs_default_storage": "memory"
}

Requirements: None Data persistence: None - data is lost when application stops Best for: Unit testing, temporary operations, development scenarios

Storage Behavior

  • Agent data (agent definitions, signatures) are stored using the configured backend
  • Documents are stored using the configured backend
  • Cryptographic keys are stored using the configured backend
  • Observability data (logs, metrics) can use separate storage via observability configuration

Configuration Examples

Development Setup (Filesystem)

{
  "jacs_default_storage": "fs",
  "jacs_data_directory": "./dev_data",
  "jacs_key_directory": "./dev_keys"
}

Production Setup (AWS S3)

{
  "jacs_default_storage": "aws"
}

With environment variables:

export JACS_ENABLE_AWS_BUCKET_NAME="my-jacs-production-bucket"
export AWS_ACCESS_KEY_ID="AKIA..."
export AWS_SECRET_ACCESS_KEY="..."
export AWS_REGION="us-west-2"

HAI Platform Integration

{
  "jacs_default_storage": "hai"
}

With environment variable:

export HAI_STORAGE_URL="https://storage.hai.ai/v1"

Security Considerations

  • AWS S3: Ensure proper IAM permissions for bucket access
  • HAI Remote: Secure the HAI_STORAGE_URL endpoint and any required authentication
  • Filesystem: Ensure proper file system permissions for data and key directories
  • Keys: Regardless of storage backend, always set JACS_PRIVATE_KEY_PASSWORD for key encryption

Migration Between Storage Backends

When changing storage backends, you'll need to:

  1. Export existing data from the current backend
  2. Update the jacs_default_storage configuration
  3. Set any required environment variables for the new backend
  4. Import data into the new backend

JACS doesn't automatically migrate data between storage backends - this must be done manually or via custom scripts.

Error Codes

This reference documents error codes and messages you may encounter when using JACS.

CLI Exit Codes

CodeNameDescription
0SuccessOperation completed successfully
1General ErrorUnspecified error occurred
2Invalid ArgumentsCommand line arguments invalid
3File Not FoundSpecified file does not exist
4Verification FailedDocument or signature verification failed
5Signature InvalidCryptographic signature is invalid

Configuration Errors

Missing Configuration

Error: Configuration file not found: jacs.config.json

Cause: JACS cannot find the configuration file.

Solution:

# Initialize JACS to create configuration
jacs init

# Or specify a custom config path
JACS_CONFIG_PATH=./custom.config.json jacs agent verify

Invalid Configuration

Error: Invalid configuration: missing required field 'jacs_key_directory'

Cause: Configuration file is missing required fields.

Solution: Ensure your jacs.config.json contains all required fields:

{
  "jacs_data_directory": "./jacs_data",
  "jacs_key_directory": "./jacs_keys",
  "jacs_agent_key_algorithm": "ring-Ed25519",
  "jacs_default_storage": "fs"
}

Key Directory Not Found

Error: Key directory not found: ./jacs_keys

Cause: The specified key directory does not exist.

Solution:

# Create the directory
mkdir -p ./jacs_keys

# Or run init to create everything
jacs init

Cryptographic Errors

Private Key Not Found

Error: Private key file not found: private.pem

Cause: The private key file is missing from the key directory.

Solution:

# Generate new keys
jacs agent create --create-keys true

Invalid Key Format

Error: Failed to parse private key: invalid PEM format

Cause: The key file is corrupted or in wrong format.

Solution:

  • Regenerate keys with jacs agent create --create-keys true
  • Ensure the key file is not corrupted

Key Password Required

Error: Private key is encrypted but no password provided

Cause: Encrypted private key requires password.

Solution:

export JACS_PRIVATE_KEY_PASSWORD="your-password"
jacs document create -f doc.json

Algorithm Mismatch

Error: Key algorithm 'ring-Ed25519' does not match configured algorithm 'RSA-PSS'

Cause: The key file was created with a different algorithm than configured.

Solution:

  • Update config to match key algorithm, or
  • Regenerate keys with the correct algorithm

Signature Errors

Verification Failed

Error: Document verification failed: signature does not match content

Cause: Document content has been modified after signing.

Solution:

  • The document may have been tampered with
  • Re-sign the document if you have the original content

Missing Signature

Error: Document missing jacsSignature field

Cause: Document was not signed or signature was removed.

Solution:

# Create a signed document
jacs document create -f unsigned-doc.json

Invalid Signature Format

Error: Invalid signature format: expected base64 encoded string

Cause: The signature field is malformed.

Solution:

  • Re-sign the document
  • Verify the document hasn't been corrupted

Unknown Signing Algorithm

Error: Unknown signing algorithm: unknown-algo

Cause: Document was signed with an unsupported algorithm.

Solution:

  • Use a supported algorithm: ring-Ed25519, RSA-PSS, pq-dilithium, pq2025

DNS Verification Errors

DNSSEC Validation Failed

Error: strict DNSSEC validation failed for <owner> (TXT not authenticated). Enable DNSSEC and publish DS at registrar

Cause: DNSSEC mode was requested but the TXT response wasn't authenticated.

Solution:

  1. Enable DNSSEC for your domain zone
  2. Publish the DS record at your registrar
  3. Wait for propagation (up to 48 hours)

DNS Record Not Found

Error: DNS TXT lookup failed for <owner> (record missing or not yet propagated)

Cause: The JACS TXT record doesn't exist or hasn't propagated.

Solution:

  1. Verify the TXT record was created:
    dig _v1.agent.jacs.yourdomain.com TXT
    
  2. Wait for DNS propagation (can take up to 48 hours)
  3. Confirm record name and value are correct

DNS Required

Error: DNS TXT lookup required (domain configured) or provide embedded fingerprint

Cause: Strict DNS mode is active because a domain is configured.

Solution:

  • Publish the TXT record, or
  • Run with --no-dns during initial setup:
    jacs agent verify --no-dns
    

DNS Lookup Timeout

Error: DNS lookup timed out for <domain>

Cause: DNS server did not respond in time.

Solution:

  • Check network connectivity
  • Try again later
  • Verify DNS server is accessible

Document Errors

Invalid JSON

Error: Failed to parse document: invalid JSON at line 5

Cause: Document file contains invalid JSON.

Solution:

  • Validate JSON with a linter
  • Check for syntax errors (missing commas, quotes)

Schema Validation Failed

Error: Schema validation failed: missing required field 'amount'

Cause: Document doesn't conform to the specified schema.

Solution:

# Check which fields are required by the schema
cat schema.json | jq '.required'

# Add missing fields to your document

Document Not Found

Error: Document not found: 550e8400-e29b-41d4-a716-446655440000

Cause: The specified document ID doesn't exist in storage.

Solution:

  • Verify the document ID is correct
  • Check the storage directory

Version Mismatch

Error: Document version mismatch: expected v2, got v1

Cause: Attempting to update with incorrect base version.

Solution:

  • Get the latest version of the document
  • Apply updates to the correct version

Agreement Errors

Agreement Not Found

Error: Document has no jacsAgreement field

Cause: Attempting agreement operations on a document without an agreement.

Solution:

# Create an agreement first
jacs document create-agreement -f doc.json -i agent1-id,agent2-id

Already Signed

Error: Agent has already signed this agreement

Cause: Attempting to sign an agreement that was already signed by this agent.

Solution:

  • No action needed, the signature is already present

Not Authorized

Error: Agent is not in the agreement's agentIDs list

Cause: Attempting to sign with an agent not listed in the agreement.

Solution:

  • Only agents listed in jacsAgreement.agentIDs can sign

Agreement Locked

Error: Cannot modify document: agreement is complete

Cause: Attempting to modify a document with a completed agreement.

Solution:

  • Create a new version/agreement if changes are needed

Storage Errors

Storage Backend Error

Error: Storage error: failed to write to filesystem

Cause: Unable to write to the configured storage backend.

Solution:

  • Check filesystem permissions
  • Verify storage directory exists
  • Check disk space

AWS S3 Error

Error: S3 error: AccessDenied

Cause: AWS credentials don't have required permissions.

Solution:

  • Verify IAM permissions include s3:GetObject, s3:PutObject
  • Check bucket policy
  • Verify credentials are correct

Connection Error

Error: Failed to connect to storage: connection refused

Cause: Cannot connect to remote storage backend.

Solution:

  • Check network connectivity
  • Verify endpoint URL is correct
  • Check firewall rules

HTTP/MCP Errors

Request Verification Failed

Error: JACS request verification failed

Cause: Incoming HTTP request has invalid JACS signature.

Solution:

  • Ensure client is signing requests correctly
  • Verify client and server are using compatible keys

Response Verification Failed

Error: JACS response verification failed

Cause: Server response has invalid signature.

Solution:

  • Check server JACS configuration
  • Verify server is signing responses

Middleware Configuration Error

Error: JACSExpressMiddleware: config file not found

Cause: Middleware cannot find JACS configuration.

Solution:

app.use('/api', JACSExpressMiddleware({
  configPath: './jacs.config.json'  // Verify path is correct
}));

Debugging Tips

Enable Verbose Output

# CLI verbose mode
jacs document verify -f doc.json -v

# Environment variable
export JACS_DEBUG=true

Check Configuration

# Display current configuration
jacs config read

Verify Agent

# Verify agent is properly configured
jacs agent verify -v

Test Signing

# Create a test document
echo '{"test": true}' > test.json
jacs document create -f test.json -v

See Also

Migration Guide

This guide covers migrating between JACS versions and common migration scenarios.

Version Compatibility

JACS maintains backward compatibility for document verification:

  • Documents signed with older versions can be verified with newer versions
  • Older JACS versions cannot verify documents using newer cryptographic algorithms

Migrating from 0.2.x to 0.3.x

Configuration Changes

New Configuration Fields:

{
  "observability": {
    "logs": { "enabled": true, "level": "info" },
    "metrics": { "enabled": false },
    "tracing": { "enabled": false }
  }
}

Deprecated Fields:

  • jacs_log_level → Use observability.logs.level
  • jacs_log_file → Use observability.logs.destination

Migration Steps

  1. Update Configuration:

    # Backup current config
    cp jacs.config.json jacs.config.json.backup
    
    # Update to new format
    # Add observability section if needed
    
  2. Update Dependencies:

    # Node.js
    npm install jacsnpm@latest
    
    # Python
    pip install --upgrade jacs
    
  3. Verify Existing Documents:

    jacs document verify -d ./jacs_data/documents/
    

Migrating Storage Backends

Filesystem to AWS S3

  1. Create S3 Bucket:

    aws s3 mb s3://my-jacs-bucket
    
  2. Update Configuration:

    {
      "jacs_default_storage": "aws",
      "jacs_data_directory": "s3://my-jacs-bucket/data"
    }
    
  3. Set Environment Variables:

    export AWS_ACCESS_KEY_ID="your-key"
    export AWS_SECRET_ACCESS_KEY="your-secret"
    export AWS_REGION="us-east-1"
    
  4. Migrate Documents:

    # Upload existing documents
    aws s3 sync ./jacs_data/ s3://my-jacs-bucket/data/
    
  5. Verify Migration:

    jacs document verify -d s3://my-jacs-bucket/data/documents/
    

AWS S3 to Filesystem

  1. Download Documents:

    aws s3 sync s3://my-jacs-bucket/data/ ./jacs_data/
    
  2. Update Configuration:

    {
      "jacs_default_storage": "fs",
      "jacs_data_directory": "./jacs_data"
    }
    
  3. Verify Documents:

    jacs document verify -d ./jacs_data/documents/
    

Migrating Cryptographic Algorithms

Ed25519 to Post-Quantum

For increased security, you may want to migrate to post-quantum algorithms.

  1. Create New Agent with New Algorithm:

    {
      "jacs_agent_key_algorithm": "pq-dilithium"
    }
    
    jacs agent create --create-keys true -f new-agent.json
    
  2. Update Configuration:

    {
      "jacs_agent_key_algorithm": "pq-dilithium",
      "jacs_agent_id_and_version": "new-agent-id:new-version"
    }
    
  3. Re-sign Critical Documents (Optional):

    // Re-sign documents with new algorithm
    const oldDoc = JSON.parse(fs.readFileSync('./old-doc.json'));
    
    // Remove old signature fields
    delete oldDoc.jacsSignature;
    delete oldDoc.jacsSha256;
    
    // Create new signed version
    const newDoc = await agent.createDocument(JSON.stringify(oldDoc));
    

Note: Old documents remain valid with old signatures. Re-signing is only needed for documents that require the new algorithm.

Migrating Between Platforms

Node.js to Python

Both platforms use the same document format:

// Node.js - create document
const signedDoc = await agent.createDocument(JSON.stringify(content));
fs.writeFileSync('doc.json', signedDoc);
# Python - verify the same document
with open('doc.json', 'r') as f:
    doc_string = f.read()

is_valid = agent.verify_document(doc_string)

Sharing Agents Between Platforms

Agents can be used across platforms by sharing configuration:

  1. Export Agent Files:

    jacs_keys/
    ├── private.pem
    └── public.pem
    jacs.config.json
    
  2. Use Same Config in Both:

    // Node.js
    await agent.load('./jacs.config.json');
    
    # Python
    agent.load('./jacs.config.json')
    

Migrating Key Formats

Unencrypted to Encrypted Keys

  1. Encrypt Existing Key:

    # Backup original
    cp jacs_keys/private.pem jacs_keys/private.pem.backup
    
    # Encrypt with password
    openssl pkcs8 -topk8 -in jacs_keys/private.pem \
      -out jacs_keys/private.pem.enc -v2 aes-256-cbc
    
    # Remove unencrypted key
    rm jacs_keys/private.pem
    mv jacs_keys/private.pem.enc jacs_keys/private.pem
    
  2. Update Configuration:

    {
      "jacs_agent_private_key_filename": "private.pem"
    }
    
  3. Set Password:

    export JACS_PRIVATE_KEY_PASSWORD="your-secure-password"
    

Database Migration

Adding Database Storage

If migrating from filesystem to include database storage:

  1. Create Database Schema:

    CREATE TABLE jacs_documents (
      id UUID PRIMARY KEY,
      version_id UUID NOT NULL,
      document JSONB NOT NULL,
      created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
      UNIQUE(id, version_id)
    );
    
  2. Import Existing Documents:

    const fs = require('fs');
    const path = require('path');
    const { Pool } = require('pg');
    
    const pool = new Pool({ connectionString: process.env.DATABASE_URL });
    const docsDir = './jacs_data/documents';
    
    async function importDocuments() {
      const docDirs = fs.readdirSync(docsDir);
    
      for (const docId of docDirs) {
        const docPath = path.join(docsDir, docId);
        const versions = fs.readdirSync(docPath);
    
        for (const versionFile of versions) {
          const docString = fs.readFileSync(
            path.join(docPath, versionFile),
            'utf-8'
          );
          const doc = JSON.parse(docString);
    
          await pool.query(`
            INSERT INTO jacs_documents (id, version_id, document)
            VALUES ($1, $2, $3)
            ON CONFLICT (id, version_id) DO NOTHING
          `, [doc.jacsId, doc.jacsVersion, doc]);
        }
      }
    }
    
    importDocuments();
    

MCP Integration Migration

Adding JACS to Existing MCP Server

  1. Install JACS:

    npm install jacsnpm
    
  2. Wrap Existing Transport:

    // Before
    const transport = new StdioServerTransport();
    await server.connect(transport);
    
    // After
    import { createJACSTransportProxy } from 'jacsnpm/mcp';
    
    const baseTransport = new StdioServerTransport();
    const secureTransport = createJACSTransportProxy(
      baseTransport,
      './jacs.config.json',
      'server'
    );
    await server.connect(secureTransport);
    
  3. Update Client:

    // Client also needs JACS
    const baseTransport = new StdioClientTransport({ command: 'node', args: ['server.js'] });
    const secureTransport = createJACSTransportProxy(
      baseTransport,
      './jacs.client.config.json',
      'client'
    );
    await client.connect(secureTransport);
    

HTTP API Migration

Adding JACS to Existing Express API

  1. Install Middleware:

    npm install jacsnpm
    
  2. Add Middleware to Routes:

    import { JACSExpressMiddleware } from 'jacsnpm/http';
    
    // Before
    app.use('/api', express.json());
    
    // After - for JACS-protected routes
    app.use('/api/secure', express.text({ type: '*/*' }));
    app.use('/api/secure', JACSExpressMiddleware({
      configPath: './jacs.config.json'
    }));
    
    // Keep non-JACS routes unchanged
    app.use('/api/public', express.json());
    
  3. Update Route Handlers:

    // Before
    app.post('/api/data', (req, res) => {
      const payload = req.body;
      // ...
    });
    
    // After
    app.post('/api/secure/data', (req, res) => {
      const payload = req.jacsPayload;  // Verified payload
      // ...
    });
    

Troubleshooting Migration

Common Issues

Documents Not Verifying After Migration:

  • Check algorithm compatibility
  • Verify keys were copied correctly
  • Ensure configuration paths are correct

Key File Errors:

  • Verify file permissions (600 for private key)
  • Check key format matches algorithm
  • Ensure password is set for encrypted keys

Storage Errors After Migration:

  • Verify storage backend is accessible
  • Check credentials/permissions
  • Ensure directory structure is correct

Verification Checklist

After any migration:

  1. Verify Configuration:

    jacs config read
    
  2. Verify Agent:

    jacs agent verify
    
  3. Verify Sample Document:

    jacs document verify -f ./sample-doc.json
    
  4. Test Document Creation:

    echo '{"test": true}' > test.json
    jacs document create -f test.json
    
  5. Verify Version:

    jacs version
    

Rollback Procedures

If migration fails:

  1. Restore Configuration:

    cp jacs.config.json.backup jacs.config.json
    
  2. Restore Keys:

    cp -r jacs_keys.backup/* jacs_keys/
    
  3. Restore Dependencies:

    # Node.js
    npm install jacsnpm@previous-version
    
    # Python
    pip install jacs==previous-version
    
  4. Verify Rollback:

    jacs agent verify
    jacs document verify -d ./jacs_data/documents/
    

See Also