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 installintegration - 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
- Core Concepts - Understand agents, documents, and agreements
- Quick Start Guide - Get up and running in minutes
- 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
- Agent A creates a task document with their requirements
- The document is signed with Agent A's private key
- A hash is calculated for integrity verification
- Agent B receives and verifies the signature and hash
- Agent B can create an agreement to accept the task
- 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
| Feature | JACS | Traditional APIs | General 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 - Learn about agents, documents, and agreements
- Quick Start - Get hands-on experience
- Implementation guides for Rust, Node.js, or Python
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
- Creation: Generate keys and initial agent document
- Registration: Store public keys for verification
- Operation: Create and sign documents
- Updates: Version changes while maintaining identity
- 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
| Field | Purpose | Example |
|---|---|---|
$schema | JSON Schema reference | URL to schema |
jacsId | Permanent document identifier | UUID v4 |
jacsVersion | Version identifier (changes on update) | UUID v4 |
jacsType | Document type | "agent", "task", "message" |
jacsVersionDate | When this version was created | RFC 3339 timestamp |
jacsOriginalVersion | Original version UUID | UUID v4 |
jacsOriginalDate | Original creation timestamp | RFC 3339 timestamp |
jacsLevel | Data level/intent | "raw", "config", "artifact", "derived" |
jacsPreviousVersion | Previous version UUID (optional) | UUID v4 or null |
jacsSha256 | Hash of document content | SHA-256 hex string |
jacsSignature | Cryptographic signature | Signature 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
- Creation: Customer agent creates task with requirements
- Delegation: Task sent to service provider agent
- Agreement: Provider signs agreement to accept task
- Execution: Provider performs the work
- Completion: Provider creates completion document
- 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
- Creation: Initial agent creates agreement with required participants
- Distribution: Agreement sent to all required agents
- Review: Each agent reviews terms and conditions
- Signing: Agents add their signatures if they consent
- Completion: Agreement becomes binding when all parties have signed
- 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
- Content Extraction: Specific fields are extracted for signing
- Canonicalization: Fields are sorted and formatted consistently
- Hashing: SHA-256 hash of the canonical content
- Signing: Private key signs the hash
- 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:
jacsIdnever changes for a document - Version IDs:
jacsVersionchanges with each update - Previous Versions:
jacsPreviousVersioncreates a chain - Timestamps:
jacsVersionDateprovides 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:
- Quick Start - Try JACS hands-on
- Choose Implementation:
- 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:
- Rust Deep Dive - Learn the full Rust API
- Node.js Integration - Add MCP support
- Python FastMCP - Build MCP servers
- Production Setup - Add monitoring and logging
- 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
From crates.io (Recommended)
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
| Feature | Description |
|---|---|
cli | Enables CLI binary build with clap and ratatui |
otlp-logs | OpenTelemetry Protocol logging backend |
otlp-metrics | OpenTelemetry Protocol metrics backend |
otlp-tracing | OpenTelemetry Protocol distributed tracing |
observability-convenience | Helper wrappers for metrics and logging |
mcp-server | Model Context Protocol server integration surface |
Platform Support
JACS supports the following platforms:
| Platform | Architecture | Support |
|---|---|---|
| Linux | x86_64, aarch64 | Full support |
| macOS | x86_64, aarch64 | Full support |
| Windows | x86_64 | Full support |
| WebAssembly | wasm32 | Partial (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:
| Variable | Description | Default |
|---|---|---|
JACS_CONFIG_PATH | Path to configuration file | ./jacs.config.json |
JACS_USE_SECURITY | Enable/disable security features | true |
JACS_DATA_DIRECTORY | Directory for document storage | ./jacs_data |
JACS_KEY_DIRECTORY | Directory for cryptographic keys | ./jacs_keys |
JACS_DEFAULT_STORAGE | Storage backend (fs, memory) | fs |
JACS_AGENT_KEY_ALGORITHM | Key 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 - Learn CLI commands
- Creating an Agent - Create your first agent
- Rust Library API - Use JACS as a library
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
| Command | Description |
|---|---|
jacs init | Initialize JACS (create config and agent with keys) |
jacs version | Print version information |
jacs config | Manage configuration |
jacs agent | Manage agents |
jacs document | Manage documents |
jacs task | Manage tasks |
Initialization
Quick Start
# Initialize everything in one step
jacs init
This command:
- Creates a configuration file (
jacs.config.json) - Generates cryptographic keys
- 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:
| Option | Short | Required | Description |
|---|---|---|---|
--create-keys | Yes | Whether to create new cryptographic keys | |
-f | No | Path 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:
| Option | Short | Description |
|---|---|---|
-a | --agent-file | Path to agent file (optional) |
--no-dns | Disable DNS validation | |
--require-dns | Require DNS validation (not strict) | |
--require-strict-dns | Require DNSSEC validation | |
--ignore-dns | Ignore 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:
| Option | Default | Description |
|---|---|---|
--domain | Domain for DNS record | |
--agent-id | Agent UUID (optional, uses config if not provided) | |
--ttl | 3600 | Time-to-live in seconds |
--encoding | base64 | Encoding format (base64, hex) |
--provider | plain | Output 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:
| Option | Short | Required | Description |
|---|---|---|---|
-n | --name | Yes | Name of the task |
-d | --description | Yes | Description of the task |
-a | --agent-file | No | Path to agent file |
-f | --filename | No | Path 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:
| Option | Short | Description |
|---|---|---|
-f | --filename | Path to input JSON file |
-d | --directory | Path to directory of JSON files |
-o | --output | Output filename |
-s | --schema | Path to custom JSON schema |
--attach | Path to file/directory for attachments | |
--embed | -e | Embed documents (true/false) |
--no-save | -n | Print to stdout instead of saving |
-v | --verbose | Enable verbose output |
-a | --agent-file | Path 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:
| Option | Short | Required | Description |
|---|---|---|---|
-f | --filename | Yes | Path to original document |
-n | --new | Yes | Path to new version |
-o | --output | No | Output filename |
--attach | No | Path to file attachments | |
--embed | -e | No | Embed 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:
| Option | Short | Description |
|---|---|---|
-f | --filename | Path to document file |
-d | --directory | Path to directory of documents |
-s | --schema | Path to JSON schema for validation |
-v | --verbose | Enable verbose output |
-a | --agent-file | Path 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:
| Option | Short | Required | Description |
|---|---|---|---|
-f | --filename | Yes | Path to document |
-i | --agentids | Yes | Comma-separated list of agent UUIDs |
-o | --output | No | Output filename |
--no-save | -n | No | Print 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
| Code | Meaning |
|---|---|
| 0 | Success |
| 1 | General error |
| 2 | Invalid arguments |
| 3 | File not found |
| 4 | Verification failed |
| 5 | Signature invalid |
Next Steps
- Creating an Agent - Detailed agent creation guide
- Working with Documents - Document operations in depth
- Agreements - Multi-agent agreements
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
Quick Method (Recommended)
# 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:
| Type | Description | Contacts Required |
|---|---|---|
ai | Fully artificial intelligence | No |
human | Individual person | Yes |
human-org | Group of people (organization) | Yes |
hybrid | Human-AI combination | Yes |
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:
| Algorithm | Description | Recommended For |
|---|---|---|
ring-Ed25519 | Fast elliptic curve signatures | General use (default) |
RSA-PSS | Traditional RSA signatures | Legacy compatibility |
pq-dilithium | Post-quantum signatures | Future-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:
- Modify the agent document
- 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
- Protect private keys: Never share or commit private keys
- Use strong algorithms: Prefer Ed25519 or post-quantum
- Enable DNS verification: For production agents
- Regular key rotation: Update keys periodically
Agent Design
- Clear service definitions: Be specific about capabilities
- Meaningful names: Use descriptive agent names
- Contact information: Include for human agents
- Version control: Track agent document changes
Operations
- Backup keys: Keep secure backups of private keys
- Monitor signatures: Watch for unauthorized signing
- Document services: Keep service definitions current
Next Steps
- Working with Documents - Create signed documents
- Agreements - Multi-agent coordination
- DNS Verification - Publish agent identity
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
| Field | Description | Auto-generated |
|---|---|---|
$schema | JSON Schema reference | Yes |
jacsId | Permanent document UUID | Yes |
jacsVersion | Version UUID (changes on update) | Yes |
jacsVersionDate | When this version was created | Yes |
jacsOriginalVersion | First version UUID | Yes |
jacsOriginalDate | Original creation timestamp | Yes |
jacsType | Document type | Yes |
jacsLevel | Data level (raw, config, artifact, derived) | Yes |
Document Levels
The jacsLevel field indicates the document's purpose:
| Level | Description | Use Case |
|---|---|---|
raw | Original data, should not change | Source documents |
config | Configuration, meant to be updated | Agent definitions, settings |
artifact | Generated output | Reports, summaries |
derived | Computed from other documents | Analysis 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:
- Hash integrity (document hasn't been modified)
- Signature validity (signature matches content)
- 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:
- Reads the original document
- Applies changes from the modified file
- Increments
jacsVersion - Links to previous version via
jacsPreviousVersion - 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
- Use appropriate levels: Match
jacsLevelto document purpose - Include context: Add descriptive fields for human readability
- Version control: Keep source files in git alongside JACS documents
Security
- Verify before trusting: Always verify signatures
- Check agent identity: Verify the signing agent
- Validate schemas: Use custom schemas for strict validation
Performance
- External attachments: Use
--embed falsefor large files - Batch processing: Use directory mode for multiple documents
- 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
- Agreements - Multi-agent consent
- Task Schema - Task document structure
- Custom Schemas - Create your own schemas
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
- Create: Initial agent creates agreement with required participants
- Distribute: Agreement document shared with all parties
- Sign: Each agent reviews and adds their signature
- 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 consentsdisagree- Agent does not consentreject- 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:
- Hash is computed from the agreement content
- Each signature includes the hash
- If content changes, hash changes, invalidating existing signatures
This prevents modifications after some parties have signed.
Best Practices
- Verify before signing: Always review documents before signing
- Check agent identities: Verify who you're agreeing with (use DNS)
- Include context: Make the agreement purpose clear
- Handle disagreement: Have a process for when agents disagree
Next Steps
- DNS Verification - Verify agent identities
- Task Schema - Task-specific agreements
- Security Model - Agreement security
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:
- Publishing an agent's public key fingerprint as a DNS TXT record
- Using DNSSEC to cryptographically verify the DNS response
- 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)
- Generate the AWS-formatted command:
jacs agent dns --domain myagent.example.com --provider aws
- 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=...\""}]
}
}]
}'
- Replace
YOUR_ZONE_IDwith your actual Route 53 hosted zone ID.
Setting Up with Cloudflare
- Generate the Cloudflare-formatted command:
jacs agent dns --domain myagent.example.com --provider cloudflare
- Or add manually in the Cloudflare dashboard:
- Type:
TXT - Name:
_v1.agent.jacs - Content:
jacs-agent-fingerprint=<your-fingerprint> - TTL: 3600
- Type:
Setting Up with Azure DNS
- Generate the Azure-formatted command:
jacs agent dns --domain myagent.example.com --provider azure
- 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
| Mode | Flag | Behavior |
|---|---|---|
| Default | (none) | Use DNS if available, fall back to local verification |
| Require DNS | --require-dns | Fail if DNS record not found (DNSSEC not required) |
| Require Strict | --require-strict-dns | Fail if DNSSEC validation fails |
| No DNS | --no-dns | Skip DNS validation entirely |
| Ignore DNS | --ignore-dns | Don'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:
- Enable DNSSEC at your registrar: Most registrars support DNSSEC
- Configure your DNS provider: Ensure your DNS provider signs zones
- 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
- Always enable DNSSEC for production agents
- Use strict validation when verifying unknown agents
- Rotate keys carefully - update DNS records before key changes
- Monitor DNS records for unauthorized changes
- 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"
- Verify the record exists:
dig _v1.agent.jacs.myagent.example.com TXT
-
Check DNS propagation (may take up to 48 hours for new records)
-
Verify the domain in the agent document matches
"DNSSEC validation failed"
- Check DNSSEC is enabled:
dig +dnssec myagent.example.com
-
Verify DS records at registrar
-
Use
--require-dnsinstead of--require-strict-dnsif DNSSEC isn't available
"Fingerprint mismatch"
- The public key may have changed - regenerate DNS record:
jacs agent dns --domain myagent.example.com
-
Update the DNS TXT record with the new fingerprint
-
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
- Creating an Agent - Set up agents with DNS domains
- Security Model - Deep dive into JACS security
- Agreements - Use DNS-verified agents in agreements
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"] }
| Feature | Description |
|---|---|
cli | CLI utilities and helpers |
observability | OpenTelemetry logging and metrics |
observability-convenience | Helper functions for observability |
full | All 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"identifiergetvalue()- Returns reference to the JSON valuegetschema()- Returns the document's schema URLsigning_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 - Logging and metrics setup
- Storage Backends - Configure different storage
- Custom Schemas - Define custom document types
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"] }
| Feature | Description |
|---|---|
observability | Core observability support |
observability-convenience | Helper functions for recording operations |
otlp-logs | OTLP log export support |
otlp-metrics | OTLP metrics export support |
otlp-tracing | OTLP 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):
tracedebuginfowarnerror
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 operationsjacs_signature_verifications- Signature verification resultsjacs_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
- Check that logging is enabled:
logs.enabled: true - Verify log level includes your log statements
- For file logging, ensure the directory is writable
Metrics Not Exporting
- Verify
otlp-metricsfeature is enabled - Check endpoint connectivity
- Confirm metrics are enabled:
metrics.enabled: true
Traces Missing
- Verify
otlp-tracingfeature is enabled - Check sampling ratio isn't filtering all traces
- Ensure spans are properly instrumented
Next Steps
- Rust Library API - Use observability in your code
- Configuration Reference - Full config options
- Advanced Topics - Security considerations
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
ring-Ed25519 (Recommended)
{
"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:
- Basic Usage - Learn core JACS operations
- MCP Integration - Add Model Context Protocol support
- HTTP Server - Create JACS HTTP APIs
- Express Middleware - Integrate with Express.js
- 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
- MCP Integration - Model Context Protocol support
- HTTP Server - Create HTTP APIs
- Express Middleware - Express.js integration
- API Reference - Complete API documentation
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:
- Outgoing messages: Signs JSON-RPC messages with the JACS agent's key using
signRequest() - Incoming messages: Verifies signatures using
verifyResponse()and extracts the payload - 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:
- Serializes the JSON-RPC message
- Calls
jacs.signRequest(message)to create a JACS artifact - 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:
- Attempts to verify it as a JACS artifact using
jacs.verifyResponse() - If valid, extracts the original JSON-RPC payload
- If not valid JACS, parses as plain JSON (fallback mode)
- 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 identifierjacsVersion- Version trackingjacsSignature- Cryptographic signaturejacsHash- 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 - Create HTTP APIs with JACS
- Express Middleware - Integrate with Express.js
- API Reference - Complete API documentation
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.bodyas 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.jacsPayloadandctx.jacsPayload
Response Processing:
- Signs
ctx.bodyif 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 - More Express integration patterns
- MCP Integration - Model Context Protocol support
- API Reference - Complete API documentation
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
- HTTP Server - Core HTTP integration concepts
- MCP Integration - Model Context Protocol support
- API Reference - Complete API documentation
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 contentcustomSchema(string, optional): Path to a custom JSON Schema for validationoutputFilename(string, optional): Filename to save the documentnoSave(boolean, optional): If true, don't save to storage (default: false)attachments(string, optional): Path to file attachmentsembed(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 stringsignatureField(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 stringattachments(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 stringagentIds(Array): Array of agent IDs required to sign question(string, optional): The agreement questioncontext(string, optional): Additional context for the agreementagreementFieldName(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 stringagreementFieldName(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 stringagreementFieldName(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 datasignatureBase64(string): The base64-encoded signaturepublicKey(Buffer): The public key as a BufferpublicKeyEncType(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 signpublicKey(Buffer): The public key as a BufferpublicKeyEncType(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 featuresjacsDataDirectory(string, optional): Directory for data storagejacsKeyDirectory(string, optional): Directory for key storagejacsAgentPrivateKeyFilename(string, optional): Private key filenamejacsAgentPublicKeyFilename(string, optional): Public key filenamejacsAgentKeyAlgorithm(string, optional): Signing algorithmjacsPrivateKeyPassword(string, optional): Password for private keyjacsAgentIdAndVersion(string, optional): Agent ID and version to loadjacsDefaultStorage(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 transportconfigPath(string): Path to JACS configuration filerole(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()- Useagent.load()signAgent()- Useagent.signAgent()verifyString()- Useagent.verifyString()signString()- Useagent.signString()verifyAgent()- Useagent.verifyAgent()updateAgent()- Useagent.updateAgent()verifyDocument()- Useagent.verifyDocument()updateDocument()- Useagent.updateDocument()verifySignature()- Useagent.verifySignature()createAgreement()- Useagent.createAgreement()signAgreement()- Useagent.signAgreement()createDocument()- Useagent.createDocument()checkAgreement()- Useagent.checkAgreement()signRequest()- Useagent.signRequest()verifyResponse()- Useagent.verifyResponse()verifyResponseWithAgentId()- Useagent.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
- Installation - Getting started
- Basic Usage - Common usage patterns
- MCP Integration - Model Context Protocol
- HTTP Server - HTTP integration
- Express Middleware - Express.js patterns
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
ring-Ed25519 (Recommended)
{
"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:
- Basic Usage - Learn core JACS operations
- MCP Integration - Add Model Context Protocol support
- FastMCP Integration - Build advanced MCP servers
- 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 - Model Context Protocol support
- FastMCP Integration - Build advanced MCP servers
- API Reference - Complete API documentation
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:
- Incoming Requests: Intercepts JSON-RPC requests and verifies them using
jacs.verify_request() - 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:
- Outgoing Messages: Signs messages using
jacs.sign_request() - 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
- Key Management: Store private keys securely
- Environment Variables: Use environment variables for sensitive paths
- Network Security: Use TLS for network transport
- 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 payloadjacs.verify_request(data)- Verify an incoming requestjacs.sign_response(data)- Sign a response payloadjacs.verify_response(data)- Verify an incoming response
Next Steps
- FastMCP Integration - Advanced FastMCP patterns
- API Reference - Complete API documentation
- Examples - More complex examples
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 contentcustom_schema(str, optional): Path to a custom JSON Schema for validationoutput_filename(str, optional): Filename to save the documentno_save(bool, optional): If True, don't save to storage (default: False)attachments(str, optional): Path to file attachmentsembed(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 stringsignature_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 stringattachments(list, optional): List of attachment file pathsembed(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 stringagent_ids(list): List of agent IDs required to signquestion(str, optional): The agreement questioncontext(str, optional): Additional context for the agreementagreement_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 stringagreement_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 stringagreement_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 datasignature_base64(str): The base64-encoded signaturepublic_key(bytes): The public key as bytespublic_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 signpublic_key(bytes): The public key as bytespublic_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
| Field | Type | Description |
|---|---|---|
jacs_agent_id_and_version | string | Agent ID and version in format "id:version" |
jacs_agent_key_algorithm | string | Signing algorithm: "ring-Ed25519", "RSA-PSS", "pq-dilithium", "pq2025" |
jacs_agent_private_key_filename | string | Private key filename |
jacs_agent_public_key_filename | string | Public key filename |
jacs_data_directory | string | Directory for data storage |
jacs_key_directory | string | Directory for key storage |
jacs_default_storage | string | Storage 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
| Exception | Description |
|---|---|
FileNotFoundError | Configuration file or key file not found |
ValueError | Invalid configuration or document format |
RuntimeError | Agent 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
- Installation - Getting started
- Basic Usage - Common usage patterns
- MCP Integration - Model Context Protocol
- Examples - More complex examples
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
| Schema | Purpose |
|---|---|
jacs.config.schema.json | Agent configuration file format |
Document Schemas
| Schema | Purpose |
|---|---|
header/v1/header.schema.json | Base fields for all JACS documents |
agent/v1/agent.schema.json | Agent identity and capabilities |
task/v1/task.schema.json | Task workflow and state management |
message/v1/message.schema.json | Inter-agent messages |
node/v1/node.schema.json | Graph node representation |
program/v1/program.schema.json | Executable program definitions |
eval/v1/eval.schema.json | Evaluation and assessment records |
Component Schemas
| Schema | Purpose |
|---|---|
signature/v1/signature.schema.json | Cryptographic signatures |
agreement/v1/agreement.schema.json | Multi-party agreements |
files/v1/files.schema.json | File attachments |
embedding/v1/embedding.schema.json | Vector embeddings |
contact/v1/contact.schema.json | Contact information |
service/v1/service.schema.json | Service definitions |
tool/v1/tool.schema.json | Tool capabilities |
action/v1/action.schema.json | Action definitions |
unit/v1/unit.schema.json | Unit 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:
| Value | Description |
|---|---|
meta | Metadata fields (IDs, dates, versions) |
base | Core cryptographic fields (hashes, signatures) |
agent | Agent-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 identifierjacsVersion- Version identifier (UUID v4)jacsVersionDate- Version timestamp (ISO 8601)jacsOriginalVersion- First version identifierjacsOriginalDate- Creation timestampjacsLevel- Document level (raw, config, artifact, derived)$schema- Schema reference URL
Format Validation
Fields use JSON Schema format keywords:
uuid- UUID v4 formatdate-time- ISO 8601 date-time formaturi- 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:
- Agent Schema - Agent identity and capabilities
- Document Schema - Document header and structure
- Task Schema - Task workflow management
- Configuration - Configuration file format
See Also
- Custom Schemas - Creating custom document types
- Security Model - How schemas relate to security
- API Reference - Using schemas in code
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:
| Type | Description |
|---|---|
human | A biological entity (individual person) |
human-org | A group of people (organization, company) |
hybrid | Combination of human and AI components |
ai | Fully 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:
| Field | Type | Required | Description |
|---|---|---|---|
jacsId | string (UUID) | Yes | Unique agent identifier |
jacsVersion | string (UUID) | Yes | Current version identifier |
jacsVersionDate | string (date-time) | Yes | Version timestamp |
jacsType | string | Yes | Set to "agent" |
jacsOriginalVersion | string (UUID) | Yes | First version identifier |
jacsOriginalDate | string (date-time) | Yes | Creation timestamp |
jacsLevel | string | Yes | Document level |
jacsSignature | object | No | Cryptographic signature |
jacsSha256 | string | No | Content hash |
Agent-Specific Fields
| Field | Type | Required | Description |
|---|---|---|---|
jacsAgentType | string | Yes | Agent classification |
jacsAgentDomain | string | No | Domain for DNS verification |
jacsServices | array | Yes | Services the agent provides |
jacsContacts | array | Conditional | Contact 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
| Field | Type | Required | Description |
|---|---|---|---|
name | string | No | Service name |
serviceDescription | string | Yes | What the service does |
successDescription | string | Yes | What success looks like |
failureDescription | string | Yes | What failure looks like |
costDescription | string | No | Pricing information |
idealCustomerDescription | string | No | Target customer profile |
termsOfService | string | No | Legal terms URL or text |
privacyPolicy | string | No | Privacy policy URL or text |
copyright | string | No | Usage rights for provided data |
eula | string | No | End-user license agreement |
isDev | boolean | No | Whether this is a dev/test service |
tools | array | No | Tool definitions |
piiDesired | array | No | Types of sensitive data needed |
PII Types
Services can declare what personally identifiable information they need:
{
"piiDesired": ["email", "phone", "address"]
}
Valid PII types:
signature- Digital signaturescryptoaddress- Cryptocurrency addressescreditcard- Payment card numbersgovid- Government identificationsocial- Social security numbersemail- Email addressesphone- Phone numbersaddress- Physical addresseszip- Postal codesPHI- Protected health informationMHI- Mental health informationidentity- Identity documentspolitical- Political affiliationbankaddress- Banking informationincome- 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
| Field | Type | Description |
|---|---|---|
firstName | string | First name |
lastName | string | Last name |
addressName | string | Location name |
phone | string | Phone number |
email | string (email) | Email address |
mailName | string | Name at address |
mailAddress | string | Street address |
mailAddressTwo | string | Address line 2 |
mailState | string | State/province |
mailZip | string | Postal code |
mailCountry | string | Country |
isPrimary | boolean | Primary 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 - Header fields documentation
- Task Schema - Task workflow schema
- DNS Verification - Setting up domain verification
- Creating an Agent - Agent creation guide
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
| Field | Type | Required | Description |
|---|---|---|---|
$schema | string | Yes | Schema URL for validation |
jacsId | string (UUID) | Yes | Unique document identifier |
jacsType | string | Yes | Document type (agent, task, etc.) |
{
"$schema": "https://hai.ai/schemas/header/v1/header.schema.json",
"jacsId": "550e8400-e29b-41d4-a716-446655440000",
"jacsType": "document"
}
Versioning
| Field | Type | Required | Description |
|---|---|---|---|
jacsVersion | string (UUID) | Yes | Current version identifier |
jacsVersionDate | string (date-time) | Yes | Version creation timestamp |
jacsPreviousVersion | string (UUID) | No | Previous version (if not first) |
jacsOriginalVersion | string (UUID) | Yes | First version identifier |
jacsOriginalDate | string (date-time) | Yes | Document creation timestamp |
jacsBranch | string (UUID) | No | Branch 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:
| Level | Description |
|---|---|
raw | Raw data that should not change |
config | Configuration meant to be updated |
artifact | Generated content that may be updated |
derived | Computed 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
| Field | Type | Required | Description |
|---|---|---|---|
agentID | string (UUID) | Yes | Signing agent's ID |
agentVersion | string (UUID) | Yes | Signing agent's version |
date | string (date-time) | Yes | Signing timestamp |
signature | string | Yes | Base64-encoded signature |
publicKeyHash | string | Yes | Hash of public key used |
signingAlgorithm | string | No | Algorithm used (ring-Ed25519, RSA-PSS, pq-dilithium) |
fields | array | Yes | Fields included in signature |
response | string | No | Text response with signature |
responseType | string | No | agree, 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
| Field | Type | Required | Description |
|---|---|---|---|
agentIDs | array | Yes | Required signers |
question | string | No | What parties are agreeing to |
context | string | No | Additional context |
signatures | array | No | Collected 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
| Field | Type | Required | Description |
|---|---|---|---|
mimetype | string | Yes | MIME type of the file |
path | string | Yes | File location (local path) |
embed | boolean | Yes | Whether to embed contents |
contents | string | No | Base64-encoded file contents |
sha256 | string | No | Hash 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
| Field | Type | Required | Description |
|---|---|---|---|
llm | string | Yes | Model used for embedding |
vector | array | Yes | Vector 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:
| Category | Description | Examples |
|---|---|---|
meta | Metadata (IDs, dates) | jacsId, jacsVersion, jacsVersionDate |
base | Cryptographic data | jacsSha256, signature |
agent | Agent-controlled content | Custom 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
jacsVersionUUID - References
jacsPreviousVersion(except the first) - All share the same
jacsIdandjacsOriginalVersion
See Also
- Agent Schema - Agent document structure
- Task Schema - Task document structure
- Working with Documents - Document operations guide
- Agreements - Multi-party agreements
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:
| State | Description |
|---|---|
creating | Task is being drafted |
rfp | Request for proposal - seeking agents |
proposal | Agent has submitted a proposal |
negotiation | Terms being negotiated |
started | Work has begun |
review | Work submitted for review |
completed | Task 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
| Field | Type | Required | Description |
|---|---|---|---|
jacsTaskName | string | No | Human-readable task name |
jacsTaskSuccess | string | No | Description of success criteria |
jacsTaskState | string | Yes | Current workflow state |
jacsTaskCustomer | object | Yes | Customer agent signature |
jacsTaskAgent | object | No | Assigned agent signature |
jacsTaskStartDate | string (date-time) | No | When work started |
jacsTaskCompleteDate | string (date-time) | No | When work completed |
jacsTaskActionsDesired | array | Yes | Required actions |
jacsStartAgreement | object | No | Agreement to begin work |
jacsEndAgreement | object | No | Agreement that work is complete |
Relationship Fields
| Field | Type | Description |
|---|---|---|
jacsTaskSubTaskOf | array | Parent task IDs |
jacsTaskCopyOf | array | Source task IDs (branching) |
jacsTaskMergedTasks | array | Tasks 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
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Action name |
description | string | Yes | What needs to be done |
tools | array | No | Tools that can be used |
cost | object | No | Cost estimate |
duration | object | No | Time estimate |
completionAgreementRequired | boolean | No | Requires 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 State | Valid Next States |
|---|---|
creating | rfp |
rfp | proposal, creating |
proposal | negotiation, rfp |
negotiation | started, proposal |
started | review |
review | completed, started |
completed | (terminal) |
See Also
- Document Schema - Base document fields
- Agent Schema - Agent structure
- Agreements - Working with agreements
- JSON Schemas Overview - Schema architecture
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
| Field | Type | Description |
|---|---|---|
jacs_data_directory | string | Path to store documents and agents |
jacs_key_directory | string | Path to store cryptographic keys |
jacs_agent_private_key_filename | string | Private key filename |
jacs_agent_public_key_filename | string | Public key filename |
jacs_agent_key_algorithm | string | Signing algorithm |
jacs_default_storage | string | Storage backend |
Configuration Options
Key Configuration
jacs_agent_key_algorithm
Specifies the cryptographic algorithm for signing:
| Value | Description |
|---|---|
ring-Ed25519 | Ed25519 signatures (recommended) |
RSA-PSS | RSA with PSS padding |
pq-dilithium | Post-quantum Dilithium |
pq2025 | Post-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:
| Value | Description |
|---|---|
fs | Local filesystem |
aws | AWS S3 storage |
hai | HAI 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
| Level | Description |
|---|---|
trace | Most verbose |
debug | Debug information |
info | General information |
warn | Warnings |
error | Errors 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
| Field | Type | Description |
|---|---|---|
ratio | number (0-1) | Percentage of traces to sample |
parent_based | boolean | Follow parent span's sampling decision |
rate_limit | integer | Max 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:
| Variable | Config Field |
|---|---|
JACS_AGENT_PRIVATE_KEY_PASSWORD | jacs_private_key_password |
JACS_DATA_DIRECTORY | jacs_data_directory |
JACS_KEY_DIRECTORY | jacs_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
- Never commit private keys - Keep keys out of version control
- Use environment variables for secrets - Don't store passwords in config files
- Enable observability - Configure logs and metrics for monitoring
- Use DNS validation - Enable
jacs_dns_validatefor additional security - Secure key directories - Restrict file permissions on key directories
chmod 700 ./jacs_keys
chmod 600 ./jacs_keys/private.pem
See Also
- JSON Schemas Overview - Schema architecture
- Observability - Monitoring guide
- DNS Verification - Domain-based verification
- Quick Start - Getting started guide
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
| Threat | Protection |
|---|---|
| Tampering | Content hashes detect modifications |
| Impersonation | Cryptographic signatures verify identity |
| Replay Attacks | Timestamps and version IDs ensure freshness |
| Man-in-the-Middle | DNS verification via DNSSEC |
| Key Compromise | Key rotation through versioning |
Trust Assumptions
- Private keys are kept secure
- Cryptographic algorithms are sound
- DNS infrastructure (when used) is trustworthy
Signature Process
Signing a Document
- Field Selection: Determine which fields to sign
- Canonicalization: Serialize fields deterministically
- Signature Generation: Sign with private key
- 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
- Hash Verification: Recompute hash and compare
- Signature Verification: Verify signature with public key
- 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:
- Generate new key pair
- Create new agent version
- Sign new version with old key
- Update configuration to use new keys
DNS-Based Verification
JACS supports DNSSEC-validated identity verification:
How It Works
- Agent publishes public key fingerprint in DNS TXT record
- Verifier queries DNS for
_v1.agent.jacs.<domain>. - DNSSEC validates the response authenticity
- 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
| Mode | Description |
|---|---|
jacs_dns_validate: false | No DNS verification |
jacs_dns_validate: true | Attempt DNS verification, allow fallback |
jacs_dns_strict: true | Require DNSSEC validation |
jacs_dns_required: true | Fail 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
- Content Lock:
jacsAgreementHashensures all parties agreed to same content - Individual Consent: Each signature records explicit agreement
- Response Types: Support for agree, disagree, or reject
- 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
| Algorithm | Type | Security Level |
|---|---|---|
ring-Ed25519 | Elliptic Curve | High (recommended) |
RSA-PSS | RSA | High |
pq-dilithium | Post-Quantum | Quantum-resistant |
pq2025 | Composite | Transitional |
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 - Algorithm details
- DNS Verification - DNS-based identity
- Configuration - Security configuration
- Agreements - Multi-party agreements
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
| Algorithm | Config Value | Type | Key Size | Signature Size | Recommended Use |
|---|---|---|---|---|---|
| Ed25519 | ring-Ed25519 | Elliptic Curve | 32 bytes | 64 bytes | General purpose (default) |
| RSA-PSS | RSA-PSS | RSA | 2048-4096 bits | 256-512 bytes | Legacy systems |
| Dilithium | pq-dilithium | Lattice-based | ~1.3 KB | ~2.4 KB | Post-quantum |
| PQ2025 | pq2025 | Hybrid | ~1.3 KB | ~2.5 KB | Transitional |
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
| Requirement | Recommended Algorithm |
|---|---|
| Best performance | ring-Ed25519 |
| Smallest signatures | ring-Ed25519 |
| Legacy compatibility | RSA-PSS |
| Quantum resistance | pq-dilithium |
| Maximum security | pq2025 |
| General purpose | ring-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
| Algorithm | Private Key Format | Public Key Format |
|---|---|---|
| ring-Ed25519 | PEM (PKCS#8) | PEM (SPKI) |
| RSA-PSS | PEM (PKCS#8) | PEM (SPKI) |
| pq-dilithium | PEM (custom) | PEM (custom) |
| pq2025 | PEM (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:
-
Generate New Keys
{ "jacs_agent_key_algorithm": "pq-dilithium" } -
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 })) -
Update Configuration
{ "jacs_agent_id_and_version": "agent-id:new-version", "jacs_agent_key_algorithm": "pq-dilithium" } -
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):
| Algorithm | Sign (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:
- Generate new key pair
- Create new agent version
- Revoke trust in compromised version
- Re-sign critical documents
See Also
- Security Model - Overall security architecture
- Configuration - Algorithm configuration
- DNS Verification - Public key fingerprint verification
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
| Backend | Config Value | Description |
|---|---|---|
| Filesystem | fs | Local file storage (default) |
| AWS S3 | aws | Amazon S3 object storage |
| HAI Cloud | hai | HAI 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
| Scenario | Recommended Backend |
|---|---|
| Development | fs |
| Single server | fs |
| Cloud deployment | aws |
| High availability | aws |
| Multi-organization | hai |
| Testing | In-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
- Configuration - Storage configuration options
- Security Model - Storage security
- Quick Start - Initial setup
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
- 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"]
}
]
}
- 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
}
}
}
Group Related Fields
{
"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"]
}
]
}
Legal Contract
{
"$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
- Create new schema version
- Update application to support both versions
- Migrate existing documents
- Deprecate old version
See Also
- JSON Schemas Overview - Built-in schemas
- Document Schema - Header fields
- Configuration - Schema configuration
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
- Python API Reference - API documentation
- Node.js API Reference - API documentation
- Security Model - Security testing considerations
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:
| Feature | Standard MCP | JACS MCP |
|---|---|---|
| Message Signing | No | Yes |
| Identity Verification | No | Yes |
| Tamper Detection | No | Yes |
| Audit Trail | No | Yes |
| Non-Repudiation | No | Yes |
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
- Outgoing Messages: The proxy intercepts JSON-RPC messages and signs them with the agent's private key
- Incoming Messages: The proxy verifies signatures before passing messages to the application
- 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/verificationcreateJACSTransportProxy()- 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 middlewareJACSMCPClient- 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:
- Try to verify as JACS artifact
- If verification fails, parse as plain JSON
- 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
| Issue | Cause | Solution |
|---|---|---|
| "JACS not operational" | Config path incorrect | Verify config file path |
| Verification failures | Incompatible keys | Ensure matching key algorithms |
| Empty responses | Null value handling | Check message serialization |
| Connection timeouts | Network issues | Verify 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
- Node.js MCP Integration - Node.js specific details
- Python MCP Integration - Python specific details
- Security Model - JACS security architecture
- Cryptographic Algorithms - Signing algorithms
- Testing - Testing MCP integrations
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-knownendpoints - 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:
- Select a key algorithm (pq2025/dilithium/rsa/ecdsa)
- Generate a cryptographic key pair
- Create an agent identity
- 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
| Option | Type | Default | Description |
|---|---|---|---|
keyAlgorithm | string | pq2025 | Signing algorithm (pq2025, pq-dilithium, rsa, ecdsa) |
autoSign | boolean | false | Automatically sign outbound messages |
autoVerify | boolean | false | Automatically verify inbound JACS-signed messages |
agentName | string | - | Human-readable agent name |
agentDescription | string | - | Agent description for A2A discovery |
agentDomain | string | - | 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
- Agent A publishes their public key at
/.well-known/jacs-pubkey.json - Agent A optionally sets DNS TXT record at
_v1.agent.jacs.<domain>. - Agent A signs a document with
jacs_sign - Agent B receives the signed document
- Agent B fetches Agent A's key with
jacs_fetch_pubkey - 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:
- Query DNS TXT record at
_v1.agent.jacs.agent.example.com - Fetch full public key from
/.well-known/jacs-pubkey.json - 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
- DNS-Based Verification - Detailed DNS setup
- Agreements - Multi-agent coordination
- MCP Integration - Model Context Protocol
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
| Framework | Language | Module |
|---|---|---|
| Express.js | Node.js | jacsnpm/http |
| Koa | Node.js | jacsnpm/http |
| FastAPI | Python | jacs.http |
| Flask | Python | jacs.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
| Issue | Cause | Solution |
|---|---|---|
req.jacsPayload undefined | Wrong middleware order | Put express.text() before JACS middleware |
| Response not signed | Sending string instead of object | Use res.send({ ... }) not res.send(JSON.stringify(...)) |
| Verification failures | Key mismatch | Ensure compatible JACS configurations |
| Connection refused | Server not running | Verify 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
- Node.js HTTP Server - Detailed Node.js documentation
- Express Middleware - Express-specific patterns
- MCP Integration - Model Context Protocol support
- Security Model - JACS security architecture
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 Type | Storage Method | Best For |
|---|---|---|
| PostgreSQL | JSONB column | Complex queries, relations |
| MongoDB | Native documents | Document-centric apps |
| SQLite | TEXT column | Local/embedded apps |
| Redis | Key-value | Caching, 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 || []
};
}
Full-Text Search
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
- Storage Backends - Built-in JACS storage
- Security Model - Document security
- Testing - Testing database integrations
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
- CLI Command Reference - Complete command reference
- Configuration Reference - Configuration options
- Rust CLI Usage - Detailed CLI documentation
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
- Node.js Installation - Setup guide
- Node.js API Reference - Complete API documentation
- MCP Integration - MCP details
- HTTP Server - HTTP integration
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
- Python Installation - Setup guide
- Python API Reference - Complete API documentation
- Python MCP Integration - MCP details
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 Examples - Command-line examples
- Node.js Examples - Node.js code examples
- Python Examples - Python code examples
- MCP Integration - MCP details
- Web Servers - HTTP integration
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 configjacs_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:
- Hash verification - Confirms document integrity
- Signature verification - Validates cryptographic signatures
- Schema validation - Ensures document structure compliance
- 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:
- Reads embedded file contents from document
- Decodes base64-encoded data
- Writes files to their original paths
- 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- Success1- 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 locationJACS_DATA_DIR- Override default data directory locationJACS_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.
| Field | Type | Required | Description |
|---|---|---|---|
enabled | boolean | Yes | Whether logging is enabled |
level | string | Yes | Minimum log level: trace, debug, info, warn, error |
destination | object | Yes | Where logs are sent (see destinations below) |
headers | object | No | Additional 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.
| Field | Type | Required | Description |
|---|---|---|---|
enabled | boolean | Yes | Whether metrics collection is enabled |
destination | object | Yes | Where metrics are exported (see destinations below) |
export_interval_seconds | integer | No | How often to export metrics (default: 60) |
headers | object | No | Additional 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.
| Field | Type | Required | Description |
|---|---|---|---|
enabled | boolean | Yes | Whether tracing is enabled |
sampling | object | No | Sampling configuration (see below) |
resource | object | No | Service identification (see below) |
Sampling Configuration
Controls which traces are collected to manage overhead.
| Field | Type | Default | Description |
|---|---|---|---|
ratio | number | 1.0 | Fraction of traces to sample (0.0-1.0) |
parent_based | boolean | true | Whether to respect parent trace sampling decisions |
rate_limit | integer | none | Maximum 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.
| Field | Type | Required | Description |
|---|---|---|---|
service_name | string | Yes | Name of the service |
service_version | string | No | Version of the service |
environment | string | No | Environment (dev, staging, prod) |
attributes | object | No | Custom 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
| Backend | Value | Description | Use Case |
|---|---|---|---|
| Filesystem | "fs" | Local file system storage | Development, single-node deployments |
| AWS S3 | "aws" | Amazon S3 object storage | Production, cloud deployments |
| HAI Remote | "hai" | HAI.ai remote storage service | HAI.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 nameAWS_ACCESS_KEY_ID- AWS access keyAWS_SECRET_ACCESS_KEY- AWS secret keyAWS_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_URLendpoint 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_PASSWORDfor key encryption
Migration Between Storage Backends
When changing storage backends, you'll need to:
- Export existing data from the current backend
- Update the
jacs_default_storageconfiguration - Set any required environment variables for the new backend
- 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
| Code | Name | Description |
|---|---|---|
| 0 | Success | Operation completed successfully |
| 1 | General Error | Unspecified error occurred |
| 2 | Invalid Arguments | Command line arguments invalid |
| 3 | File Not Found | Specified file does not exist |
| 4 | Verification Failed | Document or signature verification failed |
| 5 | Signature Invalid | Cryptographic 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:
- Enable DNSSEC for your domain zone
- Publish the DS record at your registrar
- 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:
- Verify the TXT record was created:
dig _v1.agent.jacs.yourdomain.com TXT - Wait for DNS propagation (can take up to 48 hours)
- 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-dnsduring 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.agentIDscan 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
- Configuration Reference - Configuration options
- CLI Command Reference - CLI usage
- Security Model - Security details
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→ Useobservability.logs.leveljacs_log_file→ Useobservability.logs.destination
Migration Steps
-
Update Configuration:
# Backup current config cp jacs.config.json jacs.config.json.backup # Update to new format # Add observability section if needed -
Update Dependencies:
# Node.js npm install jacsnpm@latest # Python pip install --upgrade jacs -
Verify Existing Documents:
jacs document verify -d ./jacs_data/documents/
Migrating Storage Backends
Filesystem to AWS S3
-
Create S3 Bucket:
aws s3 mb s3://my-jacs-bucket -
Update Configuration:
{ "jacs_default_storage": "aws", "jacs_data_directory": "s3://my-jacs-bucket/data" } -
Set Environment Variables:
export AWS_ACCESS_KEY_ID="your-key" export AWS_SECRET_ACCESS_KEY="your-secret" export AWS_REGION="us-east-1" -
Migrate Documents:
# Upload existing documents aws s3 sync ./jacs_data/ s3://my-jacs-bucket/data/ -
Verify Migration:
jacs document verify -d s3://my-jacs-bucket/data/documents/
AWS S3 to Filesystem
-
Download Documents:
aws s3 sync s3://my-jacs-bucket/data/ ./jacs_data/ -
Update Configuration:
{ "jacs_default_storage": "fs", "jacs_data_directory": "./jacs_data" } -
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.
-
Create New Agent with New Algorithm:
{ "jacs_agent_key_algorithm": "pq-dilithium" }jacs agent create --create-keys true -f new-agent.json -
Update Configuration:
{ "jacs_agent_key_algorithm": "pq-dilithium", "jacs_agent_id_and_version": "new-agent-id:new-version" } -
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:
-
Export Agent Files:
jacs_keys/ ├── private.pem └── public.pem jacs.config.json -
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
-
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 -
Update Configuration:
{ "jacs_agent_private_key_filename": "private.pem" } -
Set Password:
export JACS_PRIVATE_KEY_PASSWORD="your-secure-password"
Database Migration
Adding Database Storage
If migrating from filesystem to include database storage:
-
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) ); -
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
-
Install JACS:
npm install jacsnpm -
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); -
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
-
Install Middleware:
npm install jacsnpm -
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()); -
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:
-
Verify Configuration:
jacs config read -
Verify Agent:
jacs agent verify -
Verify Sample Document:
jacs document verify -f ./sample-doc.json -
Test Document Creation:
echo '{"test": true}' > test.json jacs document create -f test.json -
Verify Version:
jacs version
Rollback Procedures
If migration fails:
-
Restore Configuration:
cp jacs.config.json.backup jacs.config.json -
Restore Keys:
cp -r jacs_keys.backup/* jacs_keys/ -
Restore Dependencies:
# Node.js npm install jacsnpm@previous-version # Python pip install jacs==previous-version -
Verify Rollback:
jacs agent verify jacs document verify -d ./jacs_data/documents/
See Also
- Configuration Reference - Configuration options
- Cryptographic Algorithms - Algorithm details
- Storage Backends - Storage options