mirror of
https://github.com/humanlayer/12-factor-agents.git
synced 2025-08-20 18:59:53 +03:00
wip on package
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
.promptx
|
||||
23
12fa-a2h-plan
Normal file
23
12fa-a2h-plan
Normal file
@@ -0,0 +1,23 @@
|
||||
## Goals
|
||||
|
||||
A working implementation of
|
||||
|
||||
npx create-12-factor-agent
|
||||
|
||||
that follows the basic template in packages/create-12-factor-agent/template
|
||||
|
||||
includes by default:
|
||||
- typescript
|
||||
|
||||
|
||||
- everything in the package should be usable as a standalone package and exported, to support:
|
||||
- a npx @humanlayer/create command to create a new 12-factor agent with `humanlayer: true`
|
||||
|
||||
|
||||
### python pacakge
|
||||
|
||||
A python repo template similar to the typescript template, but uses python and uv instead of typescript.
|
||||
|
||||
A working implementation of
|
||||
`uvx create-12-factor-agent` that follows
|
||||
all the above goals, but uses python to create the repo and uses a python repo template. all other flags and toggles are the same
|
||||
91
CLAUDE.md
Normal file
91
CLAUDE.md
Normal file
@@ -0,0 +1,91 @@
|
||||
# AI Assistant Instructions
|
||||
|
||||
**IMPORTANT: Copy or merge this file into your project's CLAUDE.md file to activate agent personas.**
|
||||
|
||||
## 🚨 MANDATORY PERSONA SELECTION
|
||||
|
||||
**CRITICAL: You MUST adopt one of the specialized personas before proceeding with any work.**
|
||||
|
||||
**BEFORE DOING ANYTHING ELSE**, you must read and adopt one of these personas:
|
||||
|
||||
1. **Developer Agent** - Read `.promptx/personas/agent-developer.md` - For coding, debugging, and implementation tasks
|
||||
2. **Code Reviewer Agent** - Read `.promptx/personas/agent-code-reviewer.md` - For reviewing code changes and quality assurance
|
||||
3. **Rebaser Agent** - Read `.promptx/personas/agent-rebaser.md` - For cleaning git history and rebasing changes
|
||||
4. **Merger Agent** - Read `.promptx/personas/agent-merger.md` - For merging code across branches
|
||||
5. **Multiplan Manager Agent** - Read `.promptx/personas/agent-multiplan-manager.md` - For orchestrating parallel work and creating plans
|
||||
|
||||
**DO NOT PROCEED WITHOUT SELECTING A PERSONA.** Each persona has specific rules, workflows, and tools that you MUST follow exactly.
|
||||
|
||||
## How to Choose Your Persona
|
||||
|
||||
- **Asked to write code, fix bugs, or implement features?** → Use Developer Agent
|
||||
- **Asked to review code changes?** → Use Code Reviewer Agent
|
||||
- **Asked to clean git history or rebase changes?** → Use Rebaser Agent
|
||||
- **Asked to merge branches or consolidate work?** → Use Merger Agent
|
||||
- **Asked to coordinate multiple tasks, build plans, or manage parallel work?** → Use Multiplan Manager Agent
|
||||
|
||||
## Project Context
|
||||
|
||||
[CUSTOMIZE THIS SECTION FOR YOUR PROJECT]
|
||||
|
||||
This project uses:
|
||||
- **Language/Framework**: [Add your stack here]
|
||||
- **Build Tool**: [Add your build commands]
|
||||
- **Testing**: [Add your test commands]
|
||||
- **Architecture**: [Describe your project structure]
|
||||
|
||||
## Core Principles (All Personas)
|
||||
|
||||
1. **READ FIRST**: Always read at least 1500 lines to understand context fully
|
||||
2. **DELETE MORE THAN YOU ADD**: Complexity compounds into disasters
|
||||
3. **FOLLOW EXISTING PATTERNS**: Don't invent new approaches
|
||||
4. **BUILD AND TEST**: Run your build and test commands after changes
|
||||
5. **COMMIT FREQUENTLY**: Every 5-10 minutes for meaningful progress
|
||||
|
||||
## File Structure Reference
|
||||
|
||||
[CUSTOMIZE THIS SECTION FOR YOUR PROJECT]
|
||||
|
||||
```
|
||||
./
|
||||
├── package.json # [or your dependency file]
|
||||
├── src/ # [your source directory]
|
||||
│ ├── [your modules]
|
||||
│ └── [your files]
|
||||
├── test/ # [your test directory]
|
||||
├── .promptx/ # Agent personas (created by promptx init)
|
||||
│ └── personas/
|
||||
└── CLAUDE.md # This file (after merging)
|
||||
```
|
||||
|
||||
## Common Commands (All Personas)
|
||||
|
||||
[CUSTOMIZE THIS SECTION FOR YOUR PROJECT]
|
||||
|
||||
```bash
|
||||
# Build project
|
||||
[your build command]
|
||||
|
||||
# Run tests
|
||||
[your test command]
|
||||
|
||||
# Lint code
|
||||
[your lint command]
|
||||
|
||||
# Deploy locally
|
||||
[your deploy command]
|
||||
```
|
||||
|
||||
## CRITICAL REMINDER
|
||||
|
||||
**You CANNOT proceed without adopting a persona.** Each persona has:
|
||||
- Specific workflows and rules
|
||||
- Required tools and commands
|
||||
- Success criteria and verification steps
|
||||
- Commit and progress requirements
|
||||
|
||||
**Choose your persona now and follow its instructions exactly.**
|
||||
|
||||
---
|
||||
|
||||
*Generated by promptx - Agent personas are in .promptx/personas/*
|
||||
12
Makefile
Normal file
12
Makefile
Normal file
@@ -0,0 +1,12 @@
|
||||
# Makefile for launch compatibility
|
||||
.PHONY: setup teardown
|
||||
|
||||
setup:
|
||||
@echo "Setting up project..."
|
||||
@npm install || bun install || yarn install
|
||||
@echo "Setup complete!"
|
||||
|
||||
teardown:
|
||||
@echo "Tearing down project..."
|
||||
@rm -rf node_modules
|
||||
@echo "Teardown complete!"
|
||||
368
drafts/a2h-spec.md
Normal file
368
drafts/a2h-spec.md
Normal file
@@ -0,0 +1,368 @@
|
||||
# A2H - The Agent-to-Human Protocol
|
||||
|
||||
|
||||
## Overview
|
||||
|
||||
A2H is a service that allows an agent to request human interaction
|
||||
|
||||
|
||||
## Why another protocol?
|
||||
|
||||
MCP and A2A are not enough
|
||||
|
||||
## Shoulds
|
||||
|
||||
- Clients should respect A2H_BASE_URL and A2H_API_KEY environment variables if set, to allow for simple oauth2-based authentication to REST services.
|
||||
|
||||
## Core Protocol
|
||||
|
||||
### Scopes
|
||||
|
||||
The A2H protocol supports two scopes:
|
||||
|
||||
- The agent side, APIs consumed by an agent to request human interaction
|
||||
- The (Optional) admin side, APIs consumed by an admin or web application to manage humans and their contact channels
|
||||
|
||||
This separation allows for agents to query and find humans to contact, without exposing the human's contact details to the agent. It is the responsibility of the A2H provider to relay agent requests to the appropriate human via that human's preferred contact channel(s).
|
||||
|
||||
### Objects
|
||||
|
||||
```
|
||||
apiVersion: proto.a2h.dev/v1alpha1
|
||||
kind: Message
|
||||
metatdata:
|
||||
uid: "123"
|
||||
spec: # spec sent by agent
|
||||
message: "" # message from the agent
|
||||
response_schema:
|
||||
# optional, json schema for the response,
|
||||
channel_id:
|
||||
status: # status resolved by a2h server
|
||||
humanMessage: "" # message from the human
|
||||
response:
|
||||
# optional, matches spec schema
|
||||
```
|
||||
|
||||
```
|
||||
apiVersion: proto.a2h.dev/v1alpha1
|
||||
kind: NewConversation
|
||||
metadata:
|
||||
uid: "abc"
|
||||
spec: # spec sent by a2h server
|
||||
message: "" # message from the agent
|
||||
channel_id: "123" # channel id to use for future conversations
|
||||
response_schema:
|
||||
# optional, json schema for the response,
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#### HumanContact
|
||||
|
||||
```json
|
||||
{
|
||||
"run_id": "run_123",
|
||||
"call_id": "call_456",
|
||||
"spec": {
|
||||
"msg": "I've tried using the tool to refund the customer but its returning a 500 error. Can you help?",
|
||||
"channel": {
|
||||
"slack": {
|
||||
"channel_or_user_id": "U1234567890",
|
||||
"context_about_channel_or_user": "Support team lead"
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
A HumanContact represents a request for human interaction. It contains:
|
||||
|
||||
- `run_id` (string): Unique identifier for the run
|
||||
- `call_id` (string): Unique identifier for the contact request
|
||||
- `spec` (HumanContactSpec): The specification for the contact request
|
||||
- `status` (HumanContactStatus, optional): The current status of the contact request
|
||||
|
||||
The HumanContactSpec contains:
|
||||
- `msg` (string): The message to send to the human
|
||||
- `subject` (string, optional): Subject of the contact request
|
||||
- `channel` (ContactChannel, optional): The channel to use for contact
|
||||
- `response_options` (ResponseOption[], optional): Available response options
|
||||
- `state` (object, optional): Additional state information
|
||||
|
||||
The HumanContactStatus contains:
|
||||
- `requested_at` (datetime, optional): When the contact was requested
|
||||
- `responded_at` (datetime, optional): When the human responded
|
||||
- `response` (string, optional): The human's response
|
||||
- `response_option_name` (string, optional): Name of the selected response option
|
||||
- `slack_message_ts` (string, optional): Slack message timestamp if applicable
|
||||
- `failed_validation_details` (object, optional): Details if validation failed
|
||||
|
||||
#### FunctionCall
|
||||
|
||||
Example:
|
||||
```json
|
||||
{
|
||||
"run_id": "run_789",
|
||||
"call_id": "call_101",
|
||||
"spec": {
|
||||
"fn": "process_payment",
|
||||
"kwargs": {
|
||||
"amount": 100.00,
|
||||
"currency": "USD",
|
||||
"recipient": "merchant_123"
|
||||
},
|
||||
"channel": {
|
||||
"email": {
|
||||
"address": "ap@example.com",
|
||||
}
|
||||
},
|
||||
},
|
||||
"status": {
|
||||
"requested_at": "2024-03-20T11:00:00Z",
|
||||
"responded_at": "2024-03-20T11:02:00Z",
|
||||
"approved": true,
|
||||
"comment": "Payment looks good, approved",
|
||||
"user_info": {
|
||||
"name": "John Doe",
|
||||
"role": "Finance Manager"
|
||||
},
|
||||
"slack_message_ts": "1234567890.123457"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
A FunctionCall represents a request for human approval of a function execution. It contains:
|
||||
|
||||
- `run_id` (string): Unique identifier for the run
|
||||
- `call_id` (string): Unique identifier for the function call
|
||||
- `spec` (FunctionCallSpec): The specification for the function call
|
||||
- `status` (FunctionCallStatus, optional): The current status of the function call
|
||||
|
||||
The FunctionCallSpec contains:
|
||||
- `fn` (string): The function to be called
|
||||
- `kwargs` (object): The keyword arguments for the function
|
||||
- `channel` (ContactChannel, optional): The channel to use for contact
|
||||
- `reject_options` (ResponseOption[], optional): Available rejection options
|
||||
- `state` (object, optional): Additional state information
|
||||
|
||||
The FunctionCallStatus contains:
|
||||
- `requested_at` (datetime, optional): When the approval was requested
|
||||
- `responded_at` (datetime, optional): When the human responded
|
||||
- `approved` (boolean, optional): Whether the function call was approved
|
||||
- `comment` (string, optional): Any comment from the human
|
||||
- `user_info` (object, optional): Information about the responding user
|
||||
- `slack_context` (object, optional): Slack-specific context
|
||||
- `reject_option_name` (string, optional): Name of the selected rejection option
|
||||
- `slack_message_ts` (string, optional): Slack message timestamp if applicable
|
||||
- `failed_validation_details` (object, optional): Details if validation failed
|
||||
|
||||
#### ContactChannel
|
||||
|
||||
Example:
|
||||
```json
|
||||
{
|
||||
"slack": {
|
||||
"channel_or_user_id": "U1234567890",
|
||||
"context_about_channel_or_user": "Support team lead",
|
||||
"allowed_responder_ids": ["U1234567890", "U2345678901"],
|
||||
"experimental_slack_blocks": true,
|
||||
"thread_ts": "1234567890.123456"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```json
|
||||
{
|
||||
"email": {
|
||||
"address": "ap@example.com",
|
||||
"context_about_user": "Accounts Payable",
|
||||
"in_reply_to_message_id": "1234567890",
|
||||
"references_message_id": "1234567890",
|
||||
"template": "<html><body>...</body></html>"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
A ContactChannel represents a channel through which a human can be contacted. The protocol supports several channel types:
|
||||
|
||||
1. SlackContactChannel:
|
||||
- `channel_or_user_id` (string): The Slack channel or user ID
|
||||
- `context_about_channel_or_user` (string, optional): Additional context
|
||||
- `bot_token` (string, optional): Bot token for authentication
|
||||
- `allowed_responder_ids` (string[], optional): IDs of allowed responders
|
||||
- `experimental_slack_blocks` (boolean, optional): Enable experimental blocks
|
||||
- `thread_ts` (string, optional): Thread timestamp for threaded messages
|
||||
|
||||
2. SMSContactChannel:
|
||||
- `phone_number` (string): The phone number to contact
|
||||
- `context_about_user` (string, optional): Additional context about the user
|
||||
|
||||
3. WhatsAppContactChannel:
|
||||
- `phone_number` (string): The phone number to contact
|
||||
- `context_about_user` (string, optional): Additional context about the user
|
||||
|
||||
#### Human (Agent Side)
|
||||
|
||||
From the agent's perspective, a human is an object that has a name and description.
|
||||
|
||||
#### Human (Admin Side)
|
||||
|
||||
From the admin's perspective, a human is an object that has a name, description, and a list of prioritized contact channels, with details
|
||||
|
||||
### Agent Endpoints
|
||||
|
||||
|
||||
#### POST /human_contacts
|
||||
|
||||
#### GET /human_contacts/:call_id
|
||||
|
||||
#### POST /function_calls
|
||||
|
||||
#### GET /function_calls/:call_id
|
||||
|
||||
|
||||
## Extended Protocol
|
||||
|
||||
- Admin Humans
|
||||
- Agent Humans Get
|
||||
- Agent Humans Search
|
||||
- Agent Channels List
|
||||
- Agent Channels validate
|
||||
|
||||
### Objects
|
||||
|
||||
#### Human (Agent Side)
|
||||
|
||||
From the agent's perspective, a human is an object that has a name and description.
|
||||
|
||||
#### Human (Admin Side)
|
||||
|
||||
From the admin's perspective, a human is an object that has a name, description, and a list of prioritized contact channels, with details
|
||||
|
||||
### Agent Endpoints
|
||||
|
||||
#### GET /channels
|
||||
|
||||
return what contact channels are available and their supported fields
|
||||
|
||||
example response:
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"slack": {
|
||||
"channelOrUserId": {
|
||||
"type": "string",
|
||||
"description": "The Slack channel or user ID to send messages to"
|
||||
},
|
||||
"contextAboutChannelOrUser": {
|
||||
"type": "string",
|
||||
"description": "Additional context about the Slack channel or user"
|
||||
}
|
||||
},
|
||||
"email": {
|
||||
"address": {
|
||||
"type": "string",
|
||||
"description": "Email address to send messages to"
|
||||
},
|
||||
"contextAboutUser": {
|
||||
"type": "string",
|
||||
"description": "Additional context about the email recipient"
|
||||
},
|
||||
"inReplyToMessageId": {
|
||||
"type": "string",
|
||||
"description": "The message ID of the email to reply to"
|
||||
},
|
||||
"referencesMessageId": {
|
||||
"type": "string",
|
||||
"description": "The message ID of the email to reference"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### GET /humans
|
||||
|
||||
return a list of humans that are available to interact with
|
||||
|
||||
example response:
|
||||
|
||||
```json
|
||||
{
|
||||
"humans": [
|
||||
{
|
||||
"id": "654",
|
||||
"name": "Jane Doe",
|
||||
"description": "Jane Doe is a human who knows about technology and entrepreneurship",
|
||||
},
|
||||
{
|
||||
"id": "123",
|
||||
"name": "John Doe",
|
||||
"description": "John Doe is a human who knows about sales and marketing"
|
||||
}
|
||||
]
|
||||
}
|
||||
#### GET /humans/search?q=
|
||||
|
||||
search for humans by name or description
|
||||
|
||||
example response:
|
||||
|
||||
```json
|
||||
{
|
||||
"humans": [
|
||||
{
|
||||
"id": "654",
|
||||
"name": "Jane Doe",
|
||||
"description": "Jane Doe is a human who knows about technology and entrepreneurship",
|
||||
},
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Administrative Endpoints
|
||||
|
||||
|
||||
#### POST /humans
|
||||
|
||||
Enroll a new human for agent contact
|
||||
|
||||
example request:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "John Doe",
|
||||
"description": "John Doe is a human who knows about sales and marketing",
|
||||
"prioritizedContactChannels": [
|
||||
{
|
||||
"slack": {
|
||||
"channelOrUserId": "U1234567890",
|
||||
}
|
||||
},
|
||||
{
|
||||
"email": {
|
||||
"address": "john.doe@example.com",
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
#### GET /humans/:id
|
||||
|
||||
Get a human by id
|
||||
|
||||
example response:
|
||||
|
||||
```json
|
||||
0
drafts/ah2-openapi.json
Normal file
0
drafts/ah2-openapi.json
Normal file
47
find_a2h_spec.sh
Executable file
47
find_a2h_spec.sh
Executable file
@@ -0,0 +1,47 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Script to find A2H spec in git history across all repos
|
||||
|
||||
echo "=== Searching for A2H spec across all repositories ==="
|
||||
echo
|
||||
|
||||
REPOS=(
|
||||
"/Users/dex/go/src/github.com/humanlayer/12-factor-agents"
|
||||
"/Users/dex/go/src/github.com/humanlayer/humanlayer"
|
||||
"/Users/dex/go/src/github.com/humanlayer/agentcontrolplane"
|
||||
"/Users/dex/go/src/github.com/metalytics-dev/metalytics"
|
||||
)
|
||||
|
||||
for repo in "${REPOS[@]}"; do
|
||||
if [ -d "$repo/.git" ]; then
|
||||
echo "🔍 Searching in: $repo"
|
||||
cd "$repo"
|
||||
|
||||
echo " 📋 All branches:"
|
||||
git branch -a | grep -E "(a2h|agent.*human)" || true
|
||||
|
||||
echo " 🔍 Branches containing A2H in name:"
|
||||
git branch -a | grep -i a2h || true
|
||||
|
||||
echo " 📝 Commits mentioning A2H:"
|
||||
git log --all --oneline --grep="humanMessage" -i || true
|
||||
|
||||
echo " 📁 Files in git history containing A2H:"
|
||||
git log --all --name-only --pretty=format: | grep -i "a2h\|agent.*human" | sort -u || true
|
||||
|
||||
echo " 🔍 Search file contents in all branches:"
|
||||
for branch in $(git branch -r | grep -v HEAD | sed 's/origin\///'); do
|
||||
echo " Checking branch: $branch"
|
||||
git show "$branch" 2>/dev/null | grep -l -i "a2h\|agent.*human\|agent-to-human\|humanMessage" 2>/dev/null || true
|
||||
done
|
||||
|
||||
echo " 📋 Recent commits (last 50):"
|
||||
git log --all --oneline -50 | grep -i "spec\|a2h\|agent.*human\|humanMessage" || true
|
||||
|
||||
echo
|
||||
else
|
||||
echo "❌ Not a git repo: $repo"
|
||||
fi
|
||||
done
|
||||
|
||||
echo "=== Done searching ==="
|
||||
69
find_k8s_a2h_focused.sh
Executable file
69
find_k8s_a2h_focused.sh
Executable file
@@ -0,0 +1,69 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Focused search for kubernetes-style A2H spec
|
||||
|
||||
echo "=== Focused search for Kubernetes-style A2H declarations ==="
|
||||
echo
|
||||
|
||||
REPOS=(
|
||||
"/Users/dex/go/src/github.com/humanlayer/12-factor-agents"
|
||||
"/Users/dex/go/src/github.com/humanlayer/humanlayer"
|
||||
"/Users/dex/go/src/github.com/humanlayer/agentcontrolplane"
|
||||
"/Users/dex/go/src/github.com/metalytics-dev/metalytics"
|
||||
)
|
||||
|
||||
for repo in "${REPOS[@]}"; do
|
||||
if [ -d "$repo/.git" ]; then
|
||||
echo "🔍 Searching in: $repo"
|
||||
cd "$repo"
|
||||
|
||||
# Search for apiVersion with a2h in any markdown files
|
||||
echo " 📋 Files with apiVersion.*a2h:"
|
||||
find . -name "*.md" -not -path "*/node_modules/*" -exec grep -l "apiVersion.*a2h" {} \; 2>/dev/null || true
|
||||
|
||||
# Search for kind: followed by common A2H object names
|
||||
echo " 📋 Files with kind: HumanContact|FunctionCall|Contact:"
|
||||
find . -name "*.md" -not -path "*/node_modules/*" -exec grep -l "kind:.*\(HumanContact\|FunctionCall\|Contact\|Human\)" {} \; 2>/dev/null || true
|
||||
|
||||
# Search in all branches for files with these patterns
|
||||
echo " 📋 Git history search for apiVersion.*a2h:"
|
||||
git log --all --name-only --pretty=format: | grep "\.md$" | sort -u | head -20 | while read file; do
|
||||
if [ -n "$file" ]; then
|
||||
for branch in $(git branch -r | grep -v HEAD | sed 's/origin\///' | head -10); do
|
||||
content=$(git show "$branch:$file" 2>/dev/null)
|
||||
if echo "$content" | grep -q "apiVersion.*a2h"; then
|
||||
echo " Found apiVersion.*a2h in $branch:$file"
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
done
|
||||
|
||||
echo
|
||||
fi
|
||||
done
|
||||
|
||||
echo "=== Manual content check of key files ==="
|
||||
|
||||
# Check specific files that might contain the spec
|
||||
for repo in "${REPOS[@]}"; do
|
||||
if [ -d "$repo" ]; then
|
||||
echo "🔍 Checking key files in: $repo"
|
||||
|
||||
# Look for README, spec, docs files
|
||||
find "$repo" -name "*.md" -not -path "*/node_modules/*" | grep -E "(spec|doc|readme|acep|crd|k8s|kubernetes)" | head -10 | while read file; do
|
||||
if grep -q "kind:" "$file" 2>/dev/null; then
|
||||
echo " 📝 Found kind: in: $file"
|
||||
grep -n "kind:" "$file" 2>/dev/null | head -3
|
||||
fi
|
||||
if grep -q "apiVersion.*a2h" "$file" 2>/dev/null; then
|
||||
echo " 📝 Found apiVersion.*a2h in: $file"
|
||||
grep -n "apiVersion.*a2h" "$file" 2>/dev/null | head -3
|
||||
fi
|
||||
done
|
||||
|
||||
echo
|
||||
fi
|
||||
done
|
||||
|
||||
echo "=== Done focused search ==="
|
||||
81
find_k8s_a2h_spec.sh
Executable file
81
find_k8s_a2h_spec.sh
Executable file
@@ -0,0 +1,81 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Script to find kubernetes-style A2H spec with kind: declarations
|
||||
|
||||
echo "=== Searching for Kubernetes-style A2H spec across all repositories ==="
|
||||
echo
|
||||
|
||||
REPOS=(
|
||||
"/Users/dex/go/src/github.com/humanlayer/12-factor-agents"
|
||||
"/Users/dex/go/src/github.com/humanlayer/humanlayer"
|
||||
"/Users/dex/go/src/github.com/humanlayer/agentcontrolplane"
|
||||
"/Users/dex/go/src/github.com/metalytics-dev/metalytics"
|
||||
)
|
||||
|
||||
for repo in "${REPOS[@]}"; do
|
||||
if [ -d "$repo/.git" ]; then
|
||||
echo "🔍 Searching in: $repo"
|
||||
cd "$repo"
|
||||
|
||||
echo " 📋 Files containing 'kind:' in current branch:"
|
||||
find . -name "*.md" -type f -exec grep -l "kind:" {} \; 2>/dev/null || true
|
||||
find . -name "*.yaml" -type f -exec grep -l "kind:" {} \; 2>/dev/null || true
|
||||
find . -name "*.yml" -type f -exec grep -l "kind:" {} \; 2>/dev/null || true
|
||||
|
||||
echo " 📋 Files containing 'apiVersion.*a2h' in current branch:"
|
||||
find . -name "*.md" -type f -exec grep -l "apiVersion.*a2h" {} \; 2>/dev/null || true
|
||||
find . -name "*.yaml" -type f -exec grep -l "apiVersion.*a2h" {} \; 2>/dev/null || true
|
||||
find . -name "*.yml" -type f -exec grep -l "apiVersion.*a2h" {} \; 2>/dev/null || true
|
||||
|
||||
echo " 🔍 Searching git history for kind: declarations:"
|
||||
git log --all --name-only --pretty=format: | grep -E "\.(md|yaml|yml)$" | sort -u | while read file; do
|
||||
if [ -n "$file" ]; then
|
||||
# Check if file contains kind: in any branch
|
||||
for branch in $(git branch -r | grep -v HEAD | sed 's/origin\///'); do
|
||||
content=$(git show "$branch:$file" 2>/dev/null)
|
||||
if [[ "$content" =~ kind:[[:space:]]*[A-Za-z] ]]; then
|
||||
echo " Found kind: in $branch:$file"
|
||||
echo "$content" | grep -n "kind:" || true
|
||||
fi
|
||||
if [[ "$content" =~ apiVersion.*a2h ]]; then
|
||||
echo " Found apiVersion.*a2h in $branch:$file"
|
||||
echo "$content" | grep -n "apiVersion.*a2h" || true
|
||||
fi
|
||||
done
|
||||
fi
|
||||
done
|
||||
|
||||
echo " 🔍 Commits mentioning kubernetes-style objects:"
|
||||
git log --all --oneline --grep="kind:" --grep="apiVersion" --grep="HumanContact" --grep="FunctionCall" -i || true
|
||||
|
||||
echo " 📝 Search for specific A2H object types:"
|
||||
find . -type f \( -name "*.md" -o -name "*.yaml" -o -name "*.yml" \) -exec grep -l -i "humancontact\|functioncall\|contactchannel" {} \; 2>/dev/null || true
|
||||
|
||||
echo
|
||||
else
|
||||
echo "❌ Not a git repo: $repo"
|
||||
fi
|
||||
done
|
||||
|
||||
echo "=== Detailed content search ==="
|
||||
|
||||
# More detailed search for yaml-like content in markdown files
|
||||
for repo in "${REPOS[@]}"; do
|
||||
if [ -d "$repo/.git" ]; then
|
||||
echo "🔍 Detailed search in: $repo"
|
||||
cd "$repo"
|
||||
|
||||
# Search for yaml blocks in markdown files that contain kind:
|
||||
find . -name "*.md" -type f -exec sh -c '
|
||||
file="$1"
|
||||
# Look for yaml code blocks containing kind:
|
||||
awk "/^```(yaml|yml)$/,/^```$/" "$file" | grep -q "kind:" && echo "YAML block with kind: found in $file"
|
||||
# Look for any kind: declaration in markdown
|
||||
grep -n "kind:" "$file" 2>/dev/null && echo "kind: found in $file"
|
||||
' _ {} \; 2>/dev/null || true
|
||||
|
||||
echo
|
||||
fi
|
||||
done
|
||||
|
||||
echo "=== Done searching ==="
|
||||
@@ -8,10 +8,12 @@
|
||||
"name": "my-agent",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@boundaryml/baml": "latest",
|
||||
"express": "^5.1.0",
|
||||
"humanlayer": "^0.7.7",
|
||||
"tsx": "^4.15.0",
|
||||
"typescript": "^5.0.0"
|
||||
"typescript": "^5.0.0",
|
||||
"zod": "^3.25.64"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/express": "^5.0.1",
|
||||
@@ -22,6 +24,142 @@
|
||||
"supertest": "^7.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@boundaryml/baml": {
|
||||
"version": "0.90.0",
|
||||
"resolved": "https://registry.npmjs.org/@boundaryml/baml/-/baml-0.90.0.tgz",
|
||||
"integrity": "sha512-KNhP2XL4Kl7K8i5tQLr0L87mcGYDB2AjLwHwZpNgFineqsRCU66G+zlJuDY5KeX6xvJ11i3glz8IJrfIEzz5uw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@scarf/scarf": "^1.3.0"
|
||||
},
|
||||
"bin": {
|
||||
"baml-cli": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@boundaryml/baml-darwin-arm64": "0.90.0",
|
||||
"@boundaryml/baml-darwin-x64": "0.90.0",
|
||||
"@boundaryml/baml-linux-arm64-gnu": "0.90.0",
|
||||
"@boundaryml/baml-linux-arm64-musl": "0.90.0",
|
||||
"@boundaryml/baml-linux-x64-gnu": "0.90.0",
|
||||
"@boundaryml/baml-linux-x64-musl": "0.90.0",
|
||||
"@boundaryml/baml-win32-x64-msvc": "0.90.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@boundaryml/baml-darwin-arm64": {
|
||||
"version": "0.90.0",
|
||||
"resolved": "https://registry.npmjs.org/@boundaryml/baml-darwin-arm64/-/baml-darwin-arm64-0.90.0.tgz",
|
||||
"integrity": "sha512-cWangAtYyow9scLrwLz5zFKgG52BF2XqQzZV//YLZEEcjuSNdpewXiPSenaOrE0bF9SuyLr3pwYeeGclG0gtLQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@boundaryml/baml-darwin-x64": {
|
||||
"version": "0.90.0",
|
||||
"resolved": "https://registry.npmjs.org/@boundaryml/baml-darwin-x64/-/baml-darwin-x64-0.90.0.tgz",
|
||||
"integrity": "sha512-EzsaEaS5Hgxd7A7bHzIhfPP4efCRhCm2vmO11UGQDsT73fkDZ99NDbgItyAmVaePAkStjkrSKvT6sy0iqWA2XQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@boundaryml/baml-linux-arm64-gnu": {
|
||||
"version": "0.90.0",
|
||||
"resolved": "https://registry.npmjs.org/@boundaryml/baml-linux-arm64-gnu/-/baml-linux-arm64-gnu-0.90.0.tgz",
|
||||
"integrity": "sha512-xiLgkD6+lu1B+V5xzp4/JEwTWTDbMFsUkOnUved+udTh3Qg9lvnIa9he5lu/SCOwtLVTzYh2+Ha2SM5WsbpRmg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@boundaryml/baml-linux-arm64-musl": {
|
||||
"version": "0.90.0",
|
||||
"resolved": "https://registry.npmjs.org/@boundaryml/baml-linux-arm64-musl/-/baml-linux-arm64-musl-0.90.0.tgz",
|
||||
"integrity": "sha512-VVq/bECbNFiSPE52aljL3wj5LzArpTm5DcweT/QlPODLwnUUq/XBCikZu3p4STDzZzk6k05asgqMIrTsrE64wA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@boundaryml/baml-linux-x64-gnu": {
|
||||
"version": "0.90.0",
|
||||
"resolved": "https://registry.npmjs.org/@boundaryml/baml-linux-x64-gnu/-/baml-linux-x64-gnu-0.90.0.tgz",
|
||||
"integrity": "sha512-Xdgmy6OHCP4UsAdfT2+8AzNmc94rERJg8nD/tHvPX8M5KKlCNn4hNQCHGJTdNVCBNmD3wzSpDX1IKHxPxMDW3w==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@boundaryml/baml-linux-x64-musl": {
|
||||
"version": "0.90.0",
|
||||
"resolved": "https://registry.npmjs.org/@boundaryml/baml-linux-x64-musl/-/baml-linux-x64-musl-0.90.0.tgz",
|
||||
"integrity": "sha512-8TD2X2NlJ1+XF7IiPJIL6BftlP+lWsKzv81cns4JtodtAFY/3ntmtp4SkwmuzIdw60kMNO4O/wT/bpXUPirnZg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@boundaryml/baml-win32-x64-msvc": {
|
||||
"version": "0.90.0",
|
||||
"resolved": "https://registry.npmjs.org/@boundaryml/baml-win32-x64-msvc/-/baml-win32-x64-msvc-0.90.0.tgz",
|
||||
"integrity": "sha512-vuFVxjkT0UBZwFVNFKGJzvYjSji0ob8Dd0RMSom1cJoyIY7ka4+BCsq99oqSsvnEhNa4WiiLbmuoja3Okd78Vg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.25.4",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.4.tgz",
|
||||
@@ -632,6 +770,13 @@
|
||||
"@noble/hashes": "^1.1.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@scarf/scarf": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz",
|
||||
"integrity": "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==",
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@types/body-parser": {
|
||||
"version": "1.19.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz",
|
||||
@@ -3384,6 +3529,15 @@
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/zod": {
|
||||
"version": "3.25.64",
|
||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.64.tgz",
|
||||
"integrity": "sha512-hbP9FpSZf7pkS7hRVUrOjhwKJNyampPgtXKc3AN6DsWtoHsg2Sb4SQaS4Tcay380zSwd2VPo9G9180emBACp5g==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,8 @@
|
||||
"express": "^5.1.0",
|
||||
"humanlayer": "^0.7.7",
|
||||
"tsx": "^4.15.0",
|
||||
"typescript": "^5.0.0"
|
||||
"typescript": "^5.0.0",
|
||||
"zod": "^3.25.64"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/express": "^5.0.1",
|
||||
|
||||
53
packages/create-12-factor-agent/template/src/a2h.ts
Normal file
53
packages/create-12-factor-agent/template/src/a2h.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import { z, ZodSchema } from 'zod';
|
||||
|
||||
// Types for A2H API objects matching the new schemas
|
||||
|
||||
// Common metadata type
|
||||
export type Metadata = {
|
||||
uid: string;
|
||||
};
|
||||
|
||||
// Message sent by agent to a2h server
|
||||
type MessageSpec<T extends ZodSchema<any>> = {
|
||||
agentMessage: string; // message from the agent
|
||||
response_schema?: T; // optional Zod schema for the response
|
||||
channel_id?: string; // optional channel id
|
||||
};
|
||||
|
||||
export type Message<T extends ZodSchema<any> = ZodSchema<any>> = {
|
||||
apiVersion: "proto.a2h.dev/v1alpha1";
|
||||
kind: "Message";
|
||||
metadata: Metadata;
|
||||
spec: MessageSpec<T>;
|
||||
status?: {
|
||||
humanMessage?: string; // message from the human
|
||||
response?: T extends ZodSchema<any> ? z.infer<T> : any; // optional, matches spec schema
|
||||
};
|
||||
};
|
||||
|
||||
export const ApprovalSchema = z.object({
|
||||
approved: z.boolean(),
|
||||
comment: z.string().optional(),
|
||||
});
|
||||
|
||||
export type ApprovalRequest = Message<typeof ApprovalSchema>;
|
||||
export type HumanRequest = Message;
|
||||
|
||||
// NewConversation sent by a2h server to agent
|
||||
type NewConversationSpec = {
|
||||
user_message: string; // message from the human
|
||||
channel_id: string; // channel id to use for future conversations
|
||||
agent_name?: string; // optional agent name or identifier
|
||||
raw?: Record<string, any>; // optional raw data from the request, e.g. email metadata
|
||||
};
|
||||
|
||||
export type NewConversation = {
|
||||
apiVersion: "proto.a2h.dev/v1alpha1";
|
||||
kind: "NewConversation";
|
||||
metadata: Metadata;
|
||||
spec: NewConversationSpec;
|
||||
};
|
||||
|
||||
// Optionally, you can add union types for future extensibility
|
||||
export type A2HEvent<T extends ZodSchema<any> = ZodSchema<any>> = Message<T> | NewConversation;
|
||||
|
||||
Reference in New Issue
Block a user