diff --git a/spec/SPECIFICATION.md b/spec/SPECIFICATION.md index 89d1721..19ea1b3 100644 --- a/spec/SPECIFICATION.md +++ b/spec/SPECIFICATION.md @@ -18,6 +18,7 @@ The standard is designed to be: my-agent/ ├── agent.yaml # [REQUIRED] Agent manifest ├── SOUL.md # [REQUIRED] Identity and personality +├── identity.yaml # [OPTIONAL] Cryptographic identity binding (Ed25519) ├── RULES.md # Hard constraints and boundaries ├── DUTIES.md # Segregation of duties policy and role declaration ├── AGENTS.md # Framework-agnostic fallback instructions @@ -322,6 +323,68 @@ tags: - regulated ``` +## 3a. identity.yaml — Cryptographic Identity (optional) + +When an agent needs verifiable identity — "is this agent really who it claims to be?" — add an `identity.yaml` file. This implements the [Works With Agents Identity Protocol](https://workswithagents.dev/specs/identity.md) (L2, CC BY 4.0), binding the manifest to an Ed25519 keypair for cryptographic verification. + +Fully optional. Agents without `identity.yaml` work identically to before. Production and regulated deployments get a seam to bolt on identity without changing the core gitagent surface. + +### Schema + +JSON Schema: [identity.schema.json](schemas/identity.schema.json) + +### Required Fields + +| Field | Type | Description | +|-------|------|-------------| +| `identity_version` | string | Identity protocol version (e.g., `"1.0.0-draft"`) | +| `agent_id` | string | Unique agent identifier (lowercase, hyphens) — SHOULD match `agent.yaml` `name` | +| `public_key` | string | Ed25519 public key in `ed25519:BASE64` format | + +### Optional Fields + +| Field | Type | Description | +|-------|------|-------------| +| `key_fingerprint` | string | SHA-256 fingerprint (`sha256:HEX`) for compact verification | +| `passport_uri` | string | URI to richer identity document (W3C DID, Agent Passport, custom) | +| `did` | string | W3C Decentralized Identifier (`did:wwa:agent-name`) | +| `created_at` | string | ISO 8601 creation timestamp | +| `expires_at` | string | ISO 8601 expiry (null or omit = no expiry until rotated) | +| `hardware_binding` | object | TPM/Secure Enclave/HSM attestation (`type`, `attestation`) | +| `owner` | object | Human owner identity (`name`, `email`, `proof`) | + +### Example + +```yaml +identity_version: "1.0.0-draft" +agent_id: "loan-reviewer" +public_key: "ed25519:iV6sDfN8kL2xQ7pR3tY9aBcE1gH4jM5oU8wZ0dF..." +key_fingerprint: "sha256:a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0" +created_at: "2026-05-06T14:30:00Z" +passport_uri: "https://registry.example.com/agents/loan-reviewer/identity.json" +did: "did:wwa:loan-reviewer" +hardware_binding: + type: "tpm" + attestation: "base64encodedattestation..." +owner: + name: "Financial Services Inc." + email: "compliance@example.com" + proof: "signed-challenge-response..." +``` + +### Runtime Semantics + +1. **Signing:** Agent signs outputs with its Ed25519 private key +2. **Verification:** Downstream tools fetch `agent.yaml` + `identity.yaml`, verify signature against `public_key` +3. **Delegation:** Repo owner signs sub-agent manifests creating a verifiable parent→child chain +4. **Revocation:** Set `expires_at` or remove/revoke the public key in the registry. Pending tasks reassigned. + +### Reference Standard + +The schema and semantics are defined by the [Works With Agents Identity Protocol](https://workswithagents.dev/specs/identity.md) (v1.0.0-draft, CC BY 4.0). The gitagent `identity.yaml` is a compatible subset — all fields map 1:1 to the Identity Protocol schema. + +--- + ## 4. SOUL.md — Identity Defines who the agent *is*. Minimal valid SOUL.md is a single paragraph. diff --git a/spec/schemas/identity.schema.json b/spec/schemas/identity.schema.json new file mode 100644 index 0000000..e32ba1b --- /dev/null +++ b/spec/schemas/identity.schema.json @@ -0,0 +1,86 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://gitagent.dev/schemas/identity.schema.json", + "title": "gitagent Identity", + "description": "Schema for identity.yaml — cryptographic identity binding for gitagent manifests. Implements the Works With Agents Identity Protocol (L2, CC BY 4.0).", + "type": "object", + "required": ["identity_version", "agent_id", "public_key"], + "properties": { + "identity_version": { + "type": "string", + "pattern": "^\\d+\\.\\d+\\.\\d+(-[a-zA-Z0-9.]+)?$", + "description": "Identity protocol version this conforms to (e.g., '1.0.0-draft')", + "default": "1.0.0-draft" + }, + "agent_id": { + "type": "string", + "pattern": "^[a-z][a-z0-9-]*$", + "description": "Unique agent identifier (lowercase, hyphens allowed)" + }, + "public_key": { + "type": "string", + "pattern": "^ed25519:[A-Za-z0-9+/=]+$", + "description": "Ed25519 public key in prefixed base64 format" + }, + "key_fingerprint": { + "type": "string", + "pattern": "^sha256:[A-Fa-f0-9]{64}$", + "description": "SHA-256 fingerprint of the public key for compact identity verification" + }, + "passport_uri": { + "type": "string", + "format": "uri", + "description": "Optional URI pointing to a richer identity document (W3C DID, Agent Passport, or custom)" + }, + "did": { + "type": "string", + "pattern": "^did:[a-z]+:[a-zA-Z0-9._%-]+$", + "description": "W3C Decentralized Identifier (e.g., did:wwa:agent-name)" + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 timestamp of identity creation" + }, + "expires_at": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 expiry timestamp. Omit or set to null for no expiry (until rotated)" + }, + "hardware_binding": { + "type": "object", + "description": "Optional hardware key binding (TPM, Secure Enclave, etc.)", + "properties": { + "type": { + "type": "string", + "enum": ["tpm", "secure_enclave", "hsm", "none"], + "description": "Hardware binding type" + }, + "attestation": { + "type": "string", + "description": "Base64-encoded hardware attestation" + } + } + }, + "owner": { + "type": "object", + "description": "Optional human owner identity", + "properties": { + "name": { + "type": "string", + "description": "Owner name" + }, + "email": { + "type": "string", + "format": "email", + "description": "Owner email" + }, + "proof": { + "type": "string", + "description": "Signed challenge-response proving ownership" + } + } + } + }, + "additionalProperties": false +} diff --git a/src/adapters/gemini.ts b/src/adapters/gemini.ts index 438805a..8da1893 100644 --- a/src/adapters/gemini.ts +++ b/src/adapters/gemini.ts @@ -372,7 +372,8 @@ function buildHooksConfig(agentDir: string): Record | null { } return Object.keys(geminiHooks).length > 0 ? geminiHooks : null; - } catch { + } catch (err) { + console.warn(`Hook config parse failed: ${(err as Error).message}`); return null; } } diff --git a/src/adapters/openai.ts b/src/adapters/openai.ts index 37dc630..6d3778e 100644 --- a/src/adapters/openai.ts +++ b/src/adapters/openai.ts @@ -30,7 +30,7 @@ export function exportToOpenAI(dir: string): string { const funcName = tool.name.replace(/-/g, '_'); lines.push(`def ${funcName}(${tool.params}):`); lines.push(` """${tool.description}"""`); - lines.push(` # TODO: Implement tool logic`); + lines.push(` # Tool stub — replace with actual ${tool.name} implementation`); lines.push(` pass\n`); } } diff --git a/src/utils/skill-discovery.ts b/src/utils/skill-discovery.ts index 087ffe7..27bf731 100644 --- a/src/utils/skill-discovery.ts +++ b/src/utils/skill-discovery.ts @@ -2,6 +2,7 @@ import { existsSync, readdirSync } from 'node:fs'; import { join, resolve } from 'node:path'; import { homedir } from 'node:os'; import { loadSkillMetadata, loadSkillFull, type SkillMetadata, type ParsedSkill } from './skill-loader.js'; +import { warn } from './format.js'; export interface DiscoveredSkill { name: string; @@ -76,8 +77,8 @@ export function discoverSkills(options: DiscoveryOptions): DiscoveredSkill[] { directory: meta.directory, source, }); - } catch { - // Skip unparseable skills + } catch (err) { + warn(`Skill discovery failed: ${skillMdPath} — ${(err as Error).message}`); } } } @@ -96,8 +97,8 @@ export function discoverAndLoadSkills(options: DiscoveryOptions): ParsedSkill[] const skillMdPath = join(disc.directory, 'SKILL.md'); try { skills.push(loadSkillFull(skillMdPath)); - } catch { - // Skip skills that fail to load + } catch (err) { + warn(`Skill load failed: ${skillMdPath} — ${(err as Error).message}`); } } diff --git a/src/utils/skill-loader.ts b/src/utils/skill-loader.ts index b949e27..2b2e27f 100644 --- a/src/utils/skill-loader.ts +++ b/src/utils/skill-loader.ts @@ -1,6 +1,7 @@ import { readFileSync, existsSync, readdirSync } from 'node:fs'; import { join } from 'node:path'; import yaml from 'js-yaml'; +import { warn } from './format.js'; /** * Agent Skills standard frontmatter — matches agentskills.io spec exactly. @@ -125,8 +126,8 @@ export function loadAllSkills(skillsDir: string): ParsedSkill[] { try { skills.push(parseSkillMd(skillMdPath)); - } catch { - // Skip skills that fail to parse + } catch (err) { + warn(`Skill parse failed: ${skillMdPath} — ${(err as Error).message}`); } } @@ -150,8 +151,8 @@ export function loadAllSkillMetadata(skillsDir: string): SkillMetadata[] { try { skills.push(loadSkillMetadata(skillMdPath)); - } catch { - // Skip skills that fail to parse + } catch (err) { + warn(`Skill metadata load failed: ${skillMdPath} — ${(err as Error).message}`); } }