Skip to main content

Agents & Entities

Entities represent identities within Stoneforge - AI agents, humans, or system processes. They are the actors that create, modify, and interact with all other elements.

Entity Interface

interface Entity extends Element {
  readonly type: 'entity';
  
  // Identity
  readonly name: string;  // System-wide unique identifier
  readonly entityType: EntityTypeValue;
  
  // Cryptographic Identity (optional)
  readonly publicKey?: string;  // Ed25519 public key, base64 encoded
  
  // Organizational Hierarchy (optional)
  readonly reportsTo?: EntityId;
}
name
string
required
System-wide unique identifier name (1-100 characters, must start with letter)
entityType
EntityTypeValue
required
Classification: agent, human, or system
publicKey
string
Ed25519 public key, base64 encoded (44 characters) for cryptographic identity
reportsTo
EntityId
Manager entity reference for organizational hierarchy

Entity Types

const EntityTypeValue = {
  AGENT: 'agent',   // AI agent - automated actors performing work
  HUMAN: 'human',   // Human user - manual actors in the system
  SYSTEM: 'system', // System process - automated infrastructure
} as const;

type EntityTypeValue = 'agent' | 'human' | 'system';
AI agents are automated actors that perform work:
  • Execute tasks autonomously
  • Spawn worker processes
  • Make decisions based on prompts
  • Examples: Director, Worker, Steward

Entity Names

Entity names must be unique system-wide and follow strict validation rules.

Name Validation Rules

MIN_NAME_LENGTH
number
default:"1"
Minimum name length
MAX_NAME_LENGTH
number
default:"100"
Maximum name length
Name pattern: ^[a-zA-Z][a-zA-Z0-9_-]*$
  • Must start with a letter
  • Followed by alphanumeric, hyphen, or underscore
  • No whitespace

Reserved Names

const RESERVED_NAMES = ['system', 'anonymous', 'unknown'] as const;
Reserved names cannot be used for entity creation.

Name Validation

// Validate entity name format
validateEntityName(value: unknown): string

// Type guard
isValidEntityName(value: unknown): value is string

// Check if name is reserved
isReservedName(name: string): name is ReservedName

Creating Entities

interface CreateEntityInput {
  name: string;
  entityType: EntityTypeValue;
  createdBy: EntityId;
  
  // Optional fields
  publicKey?: string;  // Ed25519 public key, base64 encoded
  reportsTo?: EntityId;
  tags?: string[];
  metadata?: Record<string, unknown>;
}

// Create a new entity
await createEntity(input: CreateEntityInput, config?: IdGeneratorConfig): Promise<Entity>

Cryptographic Identity

Entities can have optional cryptographic identity via Ed25519 public keys.

Public Key Format

  • Ed25519 public keys are 32 bytes
  • Encoded as base64 (44 characters with padding)
  • Pattern: ^[A-Za-z0-9+/]{43}=$

Public Key Validation

// Validate public key format
validatePublicKey(value: unknown): string

// Type guard
isValidPublicKey(value: unknown): value is string

// Check if entity has cryptographic identity
hasCryptographicIdentity(entity: Entity): boolean

Key Rotation

Rotate an entity’s public key with cryptographic verification:
interface KeyRotationInput {
  newPublicKey: string;
  signature: string;  // Signed with CURRENT private key
  signedAt: string;   // ISO 8601 timestamp
}

interface KeyRotationOptions {
  maxSignatureAge?: number;  // default: 5 minutes
  skipTimestampValidation?: boolean;
}

// Rotate entity key
await rotateEntityKey(
  entity: Entity,
  input: KeyRotationInput,
  verifySignature: (message: string, signature: string, publicKey: string) => Promise<boolean>,
  options?: KeyRotationOptions
): Promise<KeyRotationResult>

Key Revocation

Revoke an entity’s public key (converts to soft identity):
interface KeyRevocationInput {
  reason?: string;
  signature: string;  // Signed with CURRENT private key
  signedAt: string;   // ISO 8601 timestamp
}

// Revoke entity key
await revokeEntityKey(
  entity: Entity,
  input: KeyRevocationInput,
  verifySignature: (message: string, signature: string, publicKey: string) => Promise<boolean>,
  options?: KeyRevocationOptions
): Promise<KeyRevocationResult>

Entity Activation

Entities can be deactivated while preserving historical references.

Deactivation

interface DeactivateEntityInput {
  reason?: string;
  deactivatedBy: EntityId;
}

// Deactivate entity
deactivateEntity(entity: Entity, input: DeactivateEntityInput): Entity
Deactivated entities:
  • Have metadata.active = false
  • Preserved for historical references
  • Should be filtered from active listings

Reactivation

// Reactivate entity
reactivateEntity(entity: Entity, reactivatedBy: EntityId): Entity

Status Checks

// Check if entity is active
isEntityActive(entity: Entity): boolean

// Check if entity is deactivated
isEntityDeactivated(entity: Entity): boolean

// Get deactivation details
getDeactivationDetails(entity: Entity): {
  deactivatedAt?: string;
  deactivatedBy?: string;
  reason?: string;
} | null

Updating Entities

interface UpdateEntityInput {
  publicKey?: string;
  reportsTo?: EntityId | null;  // null to clear
  tags?: string[];
  metadata?: Record<string, unknown>;
}

// Update entity (name and entityType are immutable)
updateEntity(entity: Entity, input: UpdateEntityInput): Entity
Entity name and entityType are immutable after creation.

Filtering and Searching

Filter Functions

// Filter by entity type
filterByEntityType<T extends Entity>(entities: T[], entityType: EntityTypeValue): T[]

// Filter by creator
filterByCreator<T extends Entity>(entities: T[], createdBy: EntityId): T[]

// Filter with/without public key
filterWithPublicKey<T extends Entity>(entities: T[]): T[]
filterWithoutPublicKey<T extends Entity>(entities: T[]): T[]

// Filter by tag
filterByTag<T extends Entity>(entities: T[], tag: string): T[]
filterByAnyTag<T extends Entity>(entities: T[], tags: string[]): T[]
filterByAllTags<T extends Entity>(entities: T[], tags: string[]): T[]

// Filter by activation status
filterActiveEntities<T extends Entity>(entities: T[]): T[]
filterDeactivatedEntities<T extends Entity>(entities: T[]): T[]

// Filter by key revocation status
filterRevokedKeyEntities<T extends Entity>(entities: T[]): T[]
filterNonRevokedKeyEntities<T extends Entity>(entities: T[]): T[]

Search Functions

// Search by name (case-insensitive substring)
searchByName<T extends Entity>(entities: T[], query: string): T[]

// Find by exact name
findByName<T extends Entity>(entities: T[], name: string): T | undefined

// Find by ID
findById<T extends Entity>(entities: T[], id: EntityId | string): T | undefined

// Check name uniqueness
isNameUnique(entities: Entity[], name: string, excludeId?: EntityId | string): boolean

Sort Functions

// Sort by name alphabetically
sortByName<T extends Entity>(entities: T[], ascending?: boolean): T[]

// Sort by creation date
sortByCreationDate<T extends Entity>(entities: T[], ascending?: boolean): T[]

// Sort by update date
sortByUpdateDate<T extends Entity>(entities: T[], ascending?: boolean): T[]

// Sort by entity type
sortByEntityType<T extends Entity>(entities: T[]): T[]

Assignment Utilities

Query items (tasks, etc.) by entity relationships:
// Get items assigned to entity
getAssignedTo<T extends Assignable>(items: T[], entityId: string): T[]

// Get items created by entity
getCreatedBy<T extends Assignable>(items: T[], entityId: string): T[]

// Get items where entity is assignee or creator
getRelatedTo<T extends Assignable>(items: T[], entityId: string): T[]

// Count assignments by entity
countAssignmentsByEntity<T extends Assignable>(items: T[]): Map<string, number>

// Get top assignees
getTopAssignees<T extends Assignable>(items: T[], limit?: number): Array<[string, number]>

// Get assignment stats for entity
getEntityAssignmentStats<T extends Assignable>(items: T[], entityId: string): {
  assignedCount: number;
  createdCount: number;
  totalRelated: number;
}

Team Membership

Query entity team relationships:
// Get teams entity belongs to
getEntityTeamMemberships<T extends TeamLike>(teams: T[], entityId: string): T[]

// Count team memberships
countEntityTeamMemberships(teams: TeamLike[], entityId: string): number

// Check if entity is in any team
isEntityInAnyTeam(teams: TeamLike[], entityId: string): boolean

// Check if entity is in specific team
isEntityInTeam(team: TeamLike, entityId: string): boolean

// Get teammates (entities sharing teams)
getTeammates<T extends TeamLike>(teams: T[], entityId: string): string[]

// Get team membership stats
getEntityTeamMembershipStats<T extends TeamLike>(teams: T[], entityId: string): {
  teamCount: number;
  teammateCount: number;
  teamIds: string[];
  teamNames: string[];
}

Utility Functions

// Get display name from metadata or fall back to name
getEntityDisplayName(entity: Entity): string

// Check if two entities have same name
entitiesHaveSameName(a: Entity, b: Entity): boolean

// Get unique tags from entities
getUniqueTags(entities: Entity[]): string[]

// Count entities by type
countByEntityType(entities: Entity[]): Record<EntityTypeValue, number>

Type Guards

// Check if value is a valid Entity
isEntity(value: unknown): value is Entity

// Comprehensive validation with detailed errors
validateEntity(value: unknown): Entity

// Validate entity type
validateEntityType(value: unknown): EntityTypeValue

Best Practices

Unique Names

Ensure entity names are unique system-wide using isNameUnique()

Soft Identity vs. Cryptographic

Use public keys for trusted operations, soft identity for general use

Deactivate, Don't Delete

Deactivate entities instead of deleting to preserve historical references

Hierarchical Organization

Use reportsTo to build organizational hierarchies for agents and humans

Build docs developers (and LLMs) love