birth script + POST /api/users endpoint

- scripts/birth.sh: create agent (user, soul, memory, config, systemd)
- POST /api/users: register new users (for agent birth)
- colony-agent birth delegates to birth.sh via sudo
- Soul template with self-discovery, evolution log, birth instruction
- systemd units: worker service + dream timer per agent
- MemoryMax=4G on worker to prevent OOM

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-29 23:11:08 +02:00
parent d47905a68f
commit 39ba317e5e
4 changed files with 253 additions and 4 deletions

View File

@@ -44,9 +44,31 @@ async fn main() {
dream::run_dream();
}
Commands::Birth { name, instruction } => {
eprintln!("colony-agent: birth '{}' with instruction: {}", name, instruction);
eprintln!("not yet implemented");
std::process::exit(1);
// Birth delegates to the shell script for now
let script = std::path::Path::new("scripts/birth.sh");
let script_path = if script.exists() {
script.to_path_buf()
} else {
// Try relative to the apes repo
let home = std::env::var("HOME").unwrap_or_default();
std::path::PathBuf::from(format!("{}/apes/scripts/birth.sh", home))
};
if !script_path.exists() {
eprintln!("birth script not found at {} or scripts/birth.sh", script_path.display());
eprintln!("run from the apes repo root, or set HOME to the agent dir");
std::process::exit(1);
}
let status = std::process::Command::new("sudo")
.args(["bash", &script_path.to_string_lossy(), &name, &instruction])
.status();
match status {
Ok(s) if s.success() => eprintln!("agent {} born!", name),
Ok(s) => { eprintln!("birth failed with status: {}", s); std::process::exit(1); }
Err(e) => { eprintln!("failed to run birth script: {}", e); std::process::exit(1); }
}
}
Commands::Status => {
eprintln!("colony-agent: status not yet implemented");

View File

@@ -40,7 +40,7 @@ async fn main() {
let app = Router::new()
.route("/api/health", get(routes::health))
.route("/api/users", get(routes::list_users))
.route("/api/users", get(routes::list_users).post(routes::create_user))
.route("/api/me", get(routes::get_me))
.route(
"/api/channels",

View File

@@ -107,6 +107,39 @@ pub async fn get_me(
}
}
#[derive(Debug, serde::Deserialize)]
pub struct CreateUser {
pub username: String,
pub display_name: String,
pub role: String,
}
pub async fn create_user(
State(state): State<AppState>,
Json(body): Json<CreateUser>,
) -> Result<impl IntoResponse> {
let id = uuid::Uuid::new_v4().to_string();
let role = match body.role.as_str() {
"agent" => "agent",
_ => "ape",
};
sqlx::query("INSERT INTO users (id, username, display_name, role) VALUES (?, ?, ?, ?)")
.bind(&id)
.bind(&body.username)
.bind(&body.display_name)
.bind(role)
.execute(&state.db)
.await?;
let row = sqlx::query_as::<_, UserRow>("SELECT * FROM users WHERE id = ?")
.bind(&id)
.fetch_one(&state.db)
.await?;
Ok((StatusCode::CREATED, Json(row.to_api())))
}
pub async fn create_channel(
State(state): State<AppState>,
Query(user_param): Query<UserParam>,