Skip to content

Repository files navigation

AgenNet

AgenNet is an experimental Agent-native coordination substrate built on existing network transports. v0.2.0-preview.4 is a Developer Preview — physical acceptance pending. It proves dynamic Capability discovery, signed bilateral Contracts, scoped Artifact access, independent verification, and evidence-gated acceptance through real multi-process and mTLS paths; it does not yet claim completed physical multi-device validation.

Install the Developer Preview

Use the Chinese or English checksum-verifying installation guide. macOS arm64/x86_64 and Linux arm64/x86_64 are supported. Windows users run the Linux build inside WSL2; native Windows is unsupported. Agent operators can use the fixed node bootstrap guide or the packaged agenet-node-bootstrap Skill. Every Invitation and passphrase stays inside the human's local controlling TTY.

Long-term vision

AgenNet's North Star is to enable authorized coordination among participating Agents and network-reachable resources without requiring a global operator, then grow that network into an Agent Society capable of organizing specialized intelligence, balancing resources, managing conflicts, and helping people pursue complex goals. We believe such a society may become a practical path toward collective AGI. This is a long-term research hypothesis, not a claim about the v0.2 implementation.

The living vision—including horizontal Agent links, vertical knowledge and institutional inheritance, an Agent Library, School, organizations, maintenance, public-safety mechanisms, and quality-of-service transport—is in docs/vision/agent-society.md.

v0.2 boundary and security dependencies

The package is now version 0.2.0-preview.4. New v0.2 objects use the explicit agenet.kernel.v0.2 kernel version. agenet.kernel.v0.1 remains identifiable only for migration diagnostics; it is not an implicit compatibility mode and does not authorize a v0.2 effect endpoint. The v0.2 wire envelope carries a Root-to-Authority-to-Node credential chain, validates its Domain, scope, profile, role, issuer, and lifetime before deserializing signed business payload bytes, and rejects direct-root v0.1 credentials with MigrationRequiredV1Credential.

There are no external v0.1 consumers in this repository's supported release surface. The loopback demo now provisions fresh v0.2 chains on every run. Retained v0.1 credential state is read only for migration diagnosis and must be regenerated; there is no v0.1 credential issuance API or v0.2 effect-path fallback.

The v0.2 baseline pins the TLS server, certificate, encryption, protected input, HMAC, network-prefix, platform-directory, clock, and macOS plist dependencies required by the subsequent bootstrap tasks. The review record, including licenses, transitive footprint, and removal boundaries, is in docs/security/dependency-review-v0.2.md.

Provisional local bootstrap state

Task 9 introduces a deliberately versioned local persistence boundary; it is not a promise that these schemas or phase choices will never change. agenet.node-config schema 2 is a bounded, deny-unknown-fields JSON document. It carries the Domain, bootstrap profile, network boundary, Directory seeds, Authority endpoint, and revocation endpoint. Private-overlay endpoints are exact IP-literal HTTPS origins with no credentials, query, fragment, DNS name, or non-root path. Plain HTTP is accepted only when the persisted boundary is explicitly loopback.

Current-host roots are resolved through the directories crate. macOS uses ~/Library/Application Support/AgenNet/; Linux uses ${XDG_CONFIG_HOME:-~/.config}/agenet/ and ${XDG_STATE_HOME:-~/.local/state}/agenet/. Config, credential chain, Ed25519 signing key, TLS certificate/key/CA, service metadata, revocation cache, journal, and process lock have separate versioned names. Managed directories must be current-user 0700 non-symlink directories; owner-only files must be regular current-user 0600 files and are opened with no-follow, nonblocking, bounded reads. Startup material is returned only after credential Root/domain/role/profile/time, signing-key, TLS chain/key, NodeId, exact IP SAN, and certificate-time validation succeeds. Each Directory seed is the exact versioned pair { endpoint, node_id }; URL-only legacy seeds fail closed.

The credential, signing key, certificate, TLS key, and CA are intentionally separate files, not a claimed multi-file transaction. A writer must hold the bootstrap state lock, publish and validate the complete set, and only then append CredentialIssued. A crash may leave a partial set, but restart loads every file and fails closed instead of deleting credentials or guessing which file won. Task 10 owns command-level retry/reconciliation of that incomplete set; Task 9 provides only the validated persisted-bundle boundary.

The local journal records this forward path:

Absent → BinaryInstalled → ReadyForEnrollment → CredentialIssued
       → ServicePrepared → Registered → Healthy

The only backward compensation is ServicePrepared → CredentialIssued; it removes no credential material. Task 10 never records ServicePrepared, because Task 11 must first publish a real service artifact. Leave moves any installed, non-Left phase to Left. Every record contains a unique operation ID, sequence, previous hash, transition, and checksum beneath a separate versioned/checksummed header. Exact operation replay is idempotent; changed reuse, illegal transitions, incomplete lines, unknown versions, corruption, and bounds violations fail closed. One owner-only nonblocking process lock is held for the store lifetime. An uncertain durable append poisons further mutation until restart; atomic replacement failures after publish are likewise reported as uncertain so restart can reconcile the visible final file. Journal schema 2 encodes this order and rejects schema 1 rather than reinterpreting its contradictory phase semantics. The journal is provisional newline-framed JSONL, not binary framing. Its lock, create/open, replay, header sync, and later appends remain anchored to one verified owner-only directory descriptor and one journal descriptor, so a pathname replacement cannot redirect publication between validation and sync. Temporary private-material files are guarded from creation through publication: any metadata, write, flush, or file-sync error unlinks the temporary name via the pinned parent descriptor. Successful rename/link publication disarms that cleanup before the parent-directory sync, so an uncertain post-publish sync error never deletes the final file. A process crash can still leave a 0600 temporary inode; automatic wildcard cleanup is intentionally unsupported.

New journal creation exposes its owner-only final name before writing and syncing the versioned header. If header write, file sync, or parent-directory sync is uncertain, opening the store fails and the file is retained. Restart accepts only a complete valid header and otherwise fails closed; explicit operator/Task 10 repair is required. AgenNet does not automatically delete the file because a complete header may already be durable even when sync reported an error.

Login-scoped bootstrap supervisor

After domain init or node join has durably reached CredentialIssued, the operator may explicitly run:

agenet node start
agenet node status --output json
agenet node stop

node start atomically publishes a real user-service definition, records ServicePrepared, and only then asks the login-scoped service manager to start it. macOS uses ~/Library/LaunchAgents/org.nexa-language.agenet.plist; Linux uses ~/.config/systemd/user/agenet.service. Every ancestor of that absolute path is opened without following symlinks and must be root/current-user owned and not group/world writable. An activation failure rolls the journal back to CredentialIssued only after the platform manager verifies the process is stopped and the exact definition is absent. Otherwise it retains ServicePrepared, returns ServiceRollbackIncomplete, and keeps all credential material. A later start republishes and reconciles an uncertain post-delete state. Repeated start and stop operations do not advance to Registered or Healthy.

The service runs the internal bootstrap supervisor. It strictly loads the persisted config, Root/Authority/Node credential chain, signing key, peer TLS identity, and schema-2 journal; holds the single-instance journal lock; and exits gracefully on termination. Task 12 attaches the v0.2 peer runtime to this same entrypoint. It loads the current revocation cache (refreshing through the pinned Authority CA if necessary), derives handlers only from verified signed roles, binds the exact configured address, performs an mTLS health probe, and registers signed provider manifests before publishing an owner-only readiness artifact. Registration/startup failure withdraws readiness and reaps the listener. runtime_ready means this base peer runtime is listening under current policy. Production domain init issues the founding node exactly the signed Directory and Requester roles with an empty provider capability ceiling and creates a separate owner-only local-control token. When both are present, the runtime merges the Requester's signed Artifact route into the private-overlay mTLS peer listener and starts a separate dynamic loopback-only pursuit listener. Its owner-only ready record contains a local endpoint and process generation, never the token. Its unauthenticated /healthz is loopback-only and returns only the Node ID, process generation, and readiness booleans. Every production peer /healthz is mTLS-only and returns only its public Node ID, process generation, runtime readiness, and current revocation boolean. Evidence collection self-probes that exact configured peer endpoint on both A and B with the validated local certificate/key/CA bundle, requires an exact TLS Node/process match, and independently requires the platform user service to report a running process. Device A also requires its local-control health to match ready and service metadata. A token without the signed role fails closed; a signed role without the token leaves the Directory available but disables pursuits. Provider enrollment creates no local-control token. Bootstrap profile is never used as an authorization role or capability grant.

A verified founding Directory additionally loads a typed founding-only Authority runtime from the existing owner-only Authority credential/signing key, CA certificate/key, invitation state, enrollment-result directory, and revocation Authority state. It never opens the Domain Root private keystore. The supervisor signs a short-lived exact-IP server leaf, self-checks separate server-auth TLS enrollment and revocation listeners, keeps revocation snapshots fresh with the online Authority key, and cancels all listeners if any required surface fails. Non-Directory credentials never load these Authority files.

These services persist only after the owning user logs in. AgenNet does not use sudo, install a system service, enable Linux linger, embed an environment file, or put secrets in the service definition.

MVP boundary

The demo runs four independent processes on dynamic loopback ports:

  • Directory/Router
  • Requester Agent
  • Source Metrics Executor
  • Independent Source Metrics Verifier

The first real Capability is source.metrics.v1: it computes a source Artifact's SHA-256 digest, byte count, line count, and non-empty line count. The Verifier independently recomputes the same metrics. Delivery does not become Accepted until verification succeeds.

The demo now uses Authority-CA mutual TLS for every peer hop, exact-IP SANs, NodeId-bound signed envelopes, current revocation snapshots, and dynamically signed Directory results. It probes 127.0.0.2 through 127.0.0.5; hosts that do not expose those aliases fall back to distinct ports on 127.0.0.1. The summary labels these as simulated_loopback_aliases_mtls or loopback_ports_mtls beneath loopback_harness. Neither is physical multi-machine evidence. It does not validate distributed failover, quota accounting, arbitrary code sandboxing, or Internet-scale discovery.

Intended command

cargo run -- demo \
  --env-file /path/to/model.env \
  --artifact fixtures/sample.rs

The environment file is read in place. It is never copied, logged, or committed.

For a production founding node on a private overlay, the operator submits the same source-metrics flow through the local Requester boundary:

agenet pursuit run \
  --env-file /path/to/model.env \
  --artifact fixtures/sample.rs \
  --output json

This command accepts no Directory, Executor, or Verifier endpoint. It retains only the allowlisted OPENAI_BASE_URL, OPENAI_API_KEY, and VLM_MODEL entries while parsing the file and immediately discards unrelated entries. It makes one strict OpenAI-compatible decision with at most one format repair and no deterministic fallback, then sends the decision and bounded Artifact bytes over the authenticated loopback listener. Provider endpoints are learned only from signed Directory Manifests. The physical acceptance runbook and redacted evidence boundary are in docs/testing/two-device-acceptance.md.

Required Walkman alias names are OPENAI_BASE_URL, OPENAI_API_KEY, and VLM_MODEL. For this command, OPENAI_BASE_URL is the complete ModelHub gemini_multimodal_inline_v1 endpoint: the adapter does not append a route, places the credential only in the ak query parameter, and sends inline text content. Only the Requester child receives these three variables. The other three children are started with a cleared environment. The demo never falls back to DeterministicDecisionAdapter; an invalid model response fails explicitly after one format-repair request.

The successful command prints one JSON summary containing:

  • four distinct PIDs, HTTPS loopback addresses, Node IDs, and state directories;
  • source and verification Contract IDs;
  • Proposed → Active → Running → Delivered → Accepted;
  • the immutable Artifact hash and both metric results;
  • LLM call count, peer HTTP request/byte counters, and phase timings;
  • the retained .local/demo/<run-id>/ directory for inspection.

Private keys and the local control token are stored with mode 0600. Runtime state, logs, private keys, tokens, and JSONL journals are ignored by Git. Peer payloads never accept a filesystem path.

Architecture

demo harness (HTTPS/mTLS; loopback aliases when assigned)
  ├─ Directory  loopback:dynamic
  ├─ Requester  loopback:dynamic ── real LLM decision
  ├─ Executor   loopback:dynamic ── source.metrics.v1
  └─ Verifier   loopback:dynamic ── source.metrics.verify.v1

Requester → Directory → signed CandidateSet
Requester → Executor  → bilateral source Contract → Delivered Evidence
Requester → Directory → signed CandidateSet
Requester → Verifier  → parent-linked verification Contract → Delivered Evidence
Requester → Executor  → signed Accepted Event

agenet demo is provisioning and test scaffolding, not a control plane. Its four credentials are least privilege: Directory-only, Requester-only, Executor-only with the executor capability ceiling, and Verifier-only with the verifier ceiling. The Requester child receives only the Directory seed; Executor and Verifier endpoints are learned from signed Capability Manifests.

Development gates

cargo fmt --check
cargo clippy --all-targets --all-features -- -D warnings
cargo test --all-targets

Tests include pure protocol/property checks, storage replay, Axum oneshot authorization and limits, Reqwest timeout/error handling, LLM JSON repair and secret sentinels, metric edge cases, and a real four-child-process loopback demo backed by a clearly synthetic local completion server.

Security boundary

Plain HTTP remains restricted to loopback. A non-loopback peer endpoint must use HTTPS with one explicit Authority CA, a required client certificate, an exact IP SAN, and one canonical noncritical AgenNet NodeId extension. Inbound requests bind that certificate NodeId to the signed envelope issuer; outbound clients additionally require the expected remote NodeId from a verified seed or signed Capability Manifest. They do not use system roots, ambient proxies, or redirects. Revoked certificates fail at the TLS boundary; stale revocation state preserves health diagnostics while effectful handlers fail closed. Signed responses complete credential-chain, role, domain, expiry, and signature verification before their verified issuer is compared with the TLS peer NodeId; an invalid signed response is never classified as a TLS identity mismatch. Client construction also requires its own leaf to contain exactly the declared local boundary IP, preventing a valid peer identity issued for one overlay address from being reused from another configured address. Both directions require the SAN extension to contain exactly one entry: the expected IP. A matching IP plus any additional IP or DNS identity is rejected. Peer private-key PEM contains exactly one PKCS#8 block; mixed or trailing key blocks fail configuration. Public /v0 routers also fail closed when neither Rustls certificate metadata nor connection-layer loopback metadata is present, so embedding a peer router in a plain non-loopback server cannot silently skip the mTLS identity boundary.

The real TLS tests currently use dynamic loopback ports to exercise the same Rustls/Reqwest handshake path. This is not evidence of physical multi-machine reachability. Separate OS processes and state directories are not claimed as a secure sandbox. The workload does not execute shell commands. A future project.build_test.v1 adapter must use Docker, a VM, or a platform sandbox before accepting untrusted code.

Developer Preview bootstrap CLI

One typed CLI path is being added for private Tailscale and WireGuard Domains:

agenet domain init --network tailscale --bind-ip <assigned-ip>
agenet domain init --network wireguard --bind-ip <assigned-ip> \
  --allowed-cidr <private-cidr>
agenet invite create --profile <base|provider|agent-candidate> --ttl 10m
agenet node join [--bind-ip <assigned-ip>]

Root passphrases and complete invitations use only the controlling terminal; they are not accepted through argv, environment variables, JSON, or ordinary stdin. node join returns credential_issued instead of claiming registration or health. The current join result has no next command; domain init advertises only the existing invite create command.

After enrollment, the lifecycle surface is:

agenet node start|stop|status
agenet credential renew
agenet node revoke <node-id>
agenet node leave
agenet uninstall [--purge]
agenet node doctor --output <text|json>

Renewal keeps the Node Ed25519 identity and rotates the TLS private key through a fresh CSR. Startup identity is stored as complete, owner-only UUID generations with exact file hashes. A single atomic active pointer selects one generation; once present, the runtime never combines new and legacy files. Renewal restarts the actual user service and requires its readiness plus new-identity mTLS health before retired-key cleanup. Cleanup uncertainty retains old material and emits a warning rather than claiming deletion.

Identity lifecycle mutations are serialized at two levels. Credential renewal and destructive purge hold one owner-only identity-operation-v1.lock across the entire workflow. Startup readers take a short shared advisory lock on the owner-only identity-generation directory, while pointer publish/rollback, inactive cleanup, and purge take it exclusively. The exclusive pointer lock is released before restarting the user service, so HostRuntime can load the newly selected generation without deadlocking. These locks coordinate honest AgenNet processes under one account; a malicious same-euid process that ignores advisory locks remains outside the v0.2 threat boundary.

node revoke is available only on the founding administrative host and requires a controlling TTY, exact NodeId confirmation, and hidden Domain Root unlock. node leave stops the service but retains identity, config, credentials, journals, and audit state; Directory outage leaves a durable pending departure. The pending record contains one exact signed request and operation ID reused by later node leave invocations, including after local Left. A Directory-signed receipt is durably validated and stored before pending state is cleared; missing or invalid receipt state is never reported as a recorded departure. Default uninstall retains all state and removes a binary only when its recorded path, owner, mode, device, inode, size, hash, version, basename, and approved per-user installation root still match. --purge is destructive, requires both the exact NodeId and PURGE on the controlling TTY, and always retains Domain Root plus founding Authority/administrative material.

Doctor is read-only and bounded. JSON output is deterministic and contains only stable check code, ok|warn|error|skipped, sanitized message, remediation link, and a stable overall exit code: 0 for all OK/skipped, 1 for warnings, 2 for any error, and 3 only when report output itself fails. It does not refresh state.

Physical two-device reachability and setup automation remain Task 14 gates.

About

A signed, contract-driven local-loopback MVP for dynamic agent coordination

Resources

Code of conduct

Contributing

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages