Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
501 changes: 388 additions & 113 deletions Cargo.lock

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ resolver = "2"
members = [
"thrum-core",
"thrumd",
"hum-paths",
"ids",
"config",
"codegen",
"drift",
"humd",
"humctl",
"hum",
"mcp",
"nest",
Expand Down
38 changes: 28 additions & 10 deletions codegen/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,7 @@ fn render_py_helpers() -> String {
from __future__ import annotations

import hashlib
import json
import os
import threading
import time
Expand Down Expand Up @@ -524,13 +525,20 @@ class WaneTracker:


def default_socket_path() -> str:
"""Resolve the humd thrum socket per WIRE.md priority:
HUM_THRUM_SOCK > $XDG_RUNTIME_DIR/hum/thrum.sock > /run/user/<uid>/hum/thrum.sock."""
"""Resolve the humd thrum socket:
HUM_THRUM_SOCK > $XDG_STATE_HOME/hum/runtime.json (rendezvous) > $XDG_STATE_HOME/hum/thrum.sock."""
explicit = os.environ.get("HUM_THRUM_SOCK")
if explicit:
return explicit
runtime = os.environ.get("XDG_RUNTIME_DIR") or f"/run/user/{os.geteuid()}"
return os.path.join(runtime, "hum", "thrum.sock")
state = os.environ.get("XDG_STATE_HOME") or os.path.join(os.path.expanduser("~"), ".local", "state")
try:
with open(os.path.join(state, "hum", "runtime.json"), "r") as f:
sock = json.load(f).get("socket")
if sock:
return sock
except (OSError, ValueError):
pass
return os.path.join(state, "hum", "thrum.sock")
"#;
SRC.to_string()
}
Expand Down Expand Up @@ -598,6 +606,7 @@ package thrum
import (
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"os"
"path/filepath"
Expand Down Expand Up @@ -689,17 +698,26 @@ func (w *WaneTracker) Behind(sigil string, remote int64) bool {
return remote > w.counters[sigil]
}

// DefaultSocketPath resolves the humd thrum socket per WIRE.md priority:
// HUM_THRUM_SOCK > $XDG_RUNTIME_DIR/hum/thrum.sock > /run/user/<uid>/hum/thrum.sock.
// DefaultSocketPath resolves the humd thrum socket:
// HUM_THRUM_SOCK > $XDG_STATE_HOME/hum/runtime.json (rendezvous) > $XDG_STATE_HOME/hum/thrum.sock.
func DefaultSocketPath() string {
if explicit := os.Getenv("HUM_THRUM_SOCK"); explicit != "" {
return explicit
}
runtime := os.Getenv("XDG_RUNTIME_DIR")
if runtime == "" {
runtime = fmt.Sprintf("/run/user/%d", os.Geteuid())
state := os.Getenv("XDG_STATE_HOME")
if state == "" {
home, _ := os.UserHomeDir()
state = filepath.Join(home, ".local", "state")
}
return filepath.Join(runtime, "hum", "thrum.sock")
if data, err := os.ReadFile(filepath.Join(state, "hum", "runtime.json")); err == nil {
var rt struct {
Socket string `json:"socket"`
}
if json.Unmarshal(data, &rt) == nil && rt.Socket != "" {
return rt.Socket
}
}
return filepath.Join(state, "hum", "thrum.sock")
}
"#;
SRC.to_string()
Expand Down
2 changes: 1 addition & 1 deletion config/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@ description = "hum.json loader — XDG-located, best-effort, defaults fill missi
[dependencies]
serde = { workspace = true }
serde_json = { workspace = true }
directories = { workspace = true }
tracing = { workspace = true }
jsonschema = "0.46.5"
hum-paths = { path = "../hum-paths" }
30 changes: 12 additions & 18 deletions config/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@

use std::path::{Path, PathBuf};

use directories::BaseDirs;
use serde::{Deserialize, Serialize};
use tracing::warn;

Expand All @@ -19,13 +18,16 @@ pub struct HumdSection {
pub permission_dusk_ms: u64,
#[serde(default = "defaults::drift_retention_days", rename = "driftRetentionDays")]
pub drift_retention_days: u32,
#[serde(default = "defaults::metrics_addr", rename = "metricsAddr")]
pub metrics_addr: String,
}

impl Default for HumdSection {
fn default() -> Self {
Self {
permission_dusk_ms: defaults::permission_dusk_ms(),
drift_retention_days: defaults::drift_retention_days(),
metrics_addr: defaults::metrics_addr(),
}
}
}
Expand Down Expand Up @@ -149,15 +151,7 @@ pub struct HumConfig {
// ── path resolution ───────────────────────────────────────────────────────

pub fn config_path() -> PathBuf {
if let Ok(xdg) = std::env::var("XDG_CONFIG_HOME") {
if !xdg.is_empty() {
return PathBuf::from(xdg).join("hum").join("hum.json");
}
}
if let Some(base) = BaseDirs::new() {
return base.config_dir().join("hum").join("hum.json");
}
PathBuf::from(".config/hum/hum.json")
hum_paths::hum_json()
}

/// Expand `~` against `$HOME`. Leaves absolute / non-tilde paths alone.
Expand Down Expand Up @@ -281,6 +275,9 @@ mod defaults {
pub fn drift_retention_days() -> u32 {
30
}
pub fn metrics_addr() -> String {
"127.0.0.1:9909".into()
}
pub fn max_active_cells() -> u32 {
4
}
Expand All @@ -291,15 +288,12 @@ mod defaults {
"claude-repl".into()
}
pub fn denied() -> Vec<PathBuf> {
[
"~/.ssh",
"~/.aws",
"~/.gnupg",
"~/.config/hum",
vec![
PathBuf::from("~/.ssh"),
PathBuf::from("~/.aws"),
PathBuf::from("~/.gnupg"),
hum_paths::config_dir(),
]
.iter()
.map(PathBuf::from)
.collect()
}
}

Expand Down
1 change: 1 addition & 0 deletions ensemble/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ description = "hum ensemble — the mesh of humds. Peer identity, transport trai

[dependencies]
thrum-core = { path = "../thrum-core" }
ids = { path = "../ids" }
tokio = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
Expand Down
4 changes: 2 additions & 2 deletions ensemble/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,7 @@ pub enum HelloParse {
pub fn hello_tone_unsigned(me: &Hid, caps: &PeerCapabilities) -> Tone {
serde_json::json!({
"chi": "hello",
"rid": format!("hello-{}", me.short()),
"rid": ids::HumId::mint().to_string(),
"from": me.to_hex(),
"humd_id": me.to_hex(),
"proto_version": caps.proto_version,
Expand All @@ -414,7 +414,7 @@ pub fn hello_tone(me: &Hid, key: &HumdKey, caps: &PeerCapabilities) -> Tone {
let sig: Signature = key.0.sign(&msg);
serde_json::json!({
"chi": "hello",
"rid": format!("hello-{}", me.short()),
"rid": ids::HumId::mint().to_string(),
"from": me.to_hex(),
"humd_id": me.to_hex(),
"pubkey": hex::encode(key.pubkey_bytes()),
Expand Down
6 changes: 3 additions & 3 deletions hives/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ In **local dev** you run `cargo run -p humd` and then launch your bee binary. Bo

In the **ensemble**, a bee on one machine reaches a humd on another over the ensemble transport. The remote humd sees the same hello and routes normally. A `peers.json` with one bootstrap entry turns this on. Nothing installs on the remote humd's disk, and the `source` URL stays purely informational.

As a **managed service** you keep a bee alive across reboots. Ship a `hives/<kind>/install` modeled on [`paid-oracle/install`](paid-oracle/install), which registers a service through [`scripts/svc.sh`](../scripts/svc.sh) as `hum-<kind>`. From there the CLI drives it.
As a **managed service** you keep a bee alive across reboots. Ship an `Orchfile` at your hive root declaring the SERVICE + RUN + RESTART (see any bundled hive for a template). `hum hive install <target>` resolves the target, builds the binary (Cargo / pnpm / Go / `build` script — detected from the marker file present), copies the Orchfile into `~/.config/hum/orch.d/`, and asks [orchd](https://github.com/adiled/orchd) to bring the bee up as a user systemd unit (Linux) or launchd agent (macOS). orchd is the supervisor; from there the CLI drives it.

```
hum hive --list # catalogue: installer, configured, running
Expand All @@ -118,7 +118,7 @@ hum bee <name|id> exit # stop it, preserving state
hum bee <name|id> reenter # graceful restart with the same identity
```

`hum bee reenter` is the supported replacement for `pkill`, because it restarts through the service manager and the bee keeps its persisted identity. `hum hive install` accepts the same dialect a bee advertises in its `source`: a bundled name, a local path, or a `github.com/<org>/<repo>/tree/<branch>/<path>` URL. Our own repo resolves to the local checkout, and a foreign one is shallow-cloned.
`hum bee reenter` is the supported replacement for `pkill`, because it restarts through orchd and the bee keeps its persisted identity. `hum hive install` accepts the same dialect a bee advertises in its `source`: a bundled name, a local path, or a `github.com/<org>/<repo>/tree/<branch>/<path>` URL. Our own repo resolves to the local checkout, and a foreign one is shallow-cloned. The target must contain an `Orchfile`.

## Discovery, optional

Expand All @@ -130,6 +130,6 @@ For mesh discovery, the ensemble gossips your manifest on `hum/hives/announce`,

## See also

- [`nest/`](../nest) defines `WorkerBee`, `ForagerBee`, `Cell`, `SpawnSpec`, and the encoders.
- [`nest/`](../nest) defines `WorkerBee`, `Cell`, `SpawnSpec`, and the encoders. Process lifecycle (supervise, tree-kill, reap) lives in `nest::lifecycle`.
- [WIRE.md](../WIRE.md) explains what the wire sees of nests and cells.
- [VOCABULARY](../VOCABULARY.md) holds the canonical entries for nest, hive, bee, worker, forager, nestler, and nestled.
3 changes: 3 additions & 0 deletions hives/anthropic-server/Orchfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
SERVICE anthropic-server
RUN ${HOME}/.local/bin/anthropic-server
RESTART always
3 changes: 2 additions & 1 deletion hives/bp7/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ description = "RFC 9171 Bundle Protocol v7 forager hive — interplanetary store

[dependencies]
thrum-core = { path = "../../thrum-core" }
ids = { path = "../../ids" }
nest-common = { path = "../common" }
ensemble = { path = "../../ensemble" }
tokio = { workspace = true }
Expand All @@ -16,7 +17,7 @@ anyhow = { workspace = true }
tracing = { workspace = true }
tracing-subscriber = { workspace = true }
bundle-protocol = { package = "bp7", version = "0.10" }
libc = "0.2"
hum-paths = { path = "../../hum-paths" }

[[bin]]
name = "bp7-forager"
Expand Down
3 changes: 3 additions & 0 deletions hives/bp7/Orchfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
SERVICE bp7
RUN ${HOME}/.local/bin/bp7-forager
RESTART always
9 changes: 3 additions & 6 deletions hives/bp7/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,23 +58,20 @@ struct Config {

impl Config {
fn from_env() -> Result<Self> {
let runtime = std::env::var("XDG_RUNTIME_DIR").unwrap_or_else(|_| {
format!("/run/user/{}", unsafe { libc::geteuid() })
});
let listen_str = std::env::var("BP7_LISTEN").unwrap_or_else(|_| DEFAULT_LISTEN.into());
Ok(Self {
listen: listen_str.parse().with_context(|| format!("parse BP7_LISTEN={listen_str}"))?,
node_eid: std::env::var("BP7_NODE_EID")
.unwrap_or_else(|_| "dtn://hum.local/inference".into()),
model: std::env::var("BP7_MODEL").unwrap_or_else(|_| "claude-sonnet-4".into()),
sock_path: std::env::var("HUM_THRUM_SOCK")
.unwrap_or_else(|_| format!("{runtime}/hum/thrum.sock")),
sock_path: hum_paths::thrum_sock_resolved().to_string_lossy().into_owned(),
})
}
}

#[tokio::main]
async fn main() -> Result<()> {
hum_paths::init();
tracing_subscriber::fmt()
.with_env_filter(
tracing_subscriber::EnvFilter::try_from_default_env()
Expand Down Expand Up @@ -176,7 +173,7 @@ async fn run_prompt(

let hello = json!({
"chi": Chi::Hello,
"rid": format!("hello-{}", now_ms()),
"rid": ids::HumId::mint().to_string(),
"from": HIVE_NAME,
"hid": hid,
"bee": ["forager"],
Expand Down
3 changes: 3 additions & 0 deletions hives/claude-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,12 @@ name = "claude-cli-worker"
path = "src/main.rs"

[dependencies]
hum-paths = { path = "../../hum-paths" }
nest = { path = "../../nest" }
nest-common = { path = "../common" }
ids = { path = "../../ids" }
tokio = { workspace = true, features = ["full"] }
command-group = { version = "5", features = ["with-tokio"] }
serde_json = { workspace = true }
anyhow = { workspace = true }
async-trait = { workspace = true }
Expand Down
3 changes: 3 additions & 0 deletions hives/claude-cli/Orchfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
SERVICE claude-cli
RUN ${HOME}/.local/bin/claude-cli-worker
RESTART always
96 changes: 0 additions & 96 deletions hives/claude-cli/install

This file was deleted.

Loading