P2P Network¶
Experimental
The P2P networking system is experimental. The protocol and configuration may change in future releases.
Lango supports decentralized agent-to-agent connectivity via libp2p. The Sovereign Agent Network (SAN) enables peer-to-peer communication with DID-based identity, zero-knowledge enhanced handshake, and a knowledge firewall for access control.
Overview¶
The P2P subsystem enables direct agent communication without centralized coordination:
- Direct connectivity -- agents connect peer-to-peer using libp2p with Noise encryption
- DID-based identity -- each agent derives a
did:lango:<pubkey>identity from its wallet - Knowledge firewall -- default deny-all ACL controls which peers can access which tools
- Agent discovery -- GossipSub-based agent card propagation for capability-based search
- ZK-enhanced handshake -- optional zero-knowledge proof verification during peer authentication
graph TB
subgraph Agent A
WA[Wallet] --> IDA[DID Provider]
IDA --> NA[P2P Node]
NA --> FWA[Firewall]
NA --> DA[Discovery]
end
subgraph Agent B
WB[Wallet] --> IDB[DID Provider]
IDB --> NB[P2P Node]
NB --> FWB[Firewall]
NB --> DB[Discovery]
end
NA <-- "Noise-encrypted\nlibp2p stream" --> NB
DA <-- "GossipSub\nagent cards" --> DB
style NA fill:#7c3aed,color:#fff
style NB fill:#7c3aed,color:#fff Identity¶
Each Lango agent derives a decentralized identifier (DID) from its wallet's compressed secp256k1 public key:
did:lango:<hex-compressed-pubkey>
The DID is deterministically mapped to a libp2p peer ID, ensuring cryptographic binding between the wallet identity and the network identity. Private keys never leave the wallet layer.
Handshake¶
When two agents connect, they perform mutual DID-based authentication:
- TCP/QUIC connection established via libp2p with Noise encryption
- DID exchange -- each peer presents its
did:lango:...identifier - Signature verification -- DID public key is verified against the peer ID
- Signed challenge -- the initiating peer sends a challenge with ECDSA signature over the canonical payload (
nonce || timestamp || senderDID). The receiver validates the signature, checks the timestamp (5-minute past window, 30-second future grace), and verifies the nonce against a TTL-based replay cache. - Session token -- a time-limited session token is issued for subsequent queries
- (Optional) ZK proof -- when
p2p.zkHandshakeis enabled, a zero-knowledge proof of identity is verified
Protocol Versioning:
| Version | Protocol ID | Features |
|---|---|---|
| v1.0 | /lango/handshake/1.0.0 | Legacy unsigned challenge (backward compatible) |
| v1.1 | /lango/handshake/1.1.0 | ECDSA signed challenge, timestamp validation, nonce replay protection |
When p2p.requireSignedChallenge is true, unsigned (v1.0) challenges are rejected. Default is false for backward compatibility.
Session tokens have a configurable TTL (p2p.sessionTokenTtl). Expired tokens require re-authentication.
Session Management¶
Sessions are managed through SessionStore with both TTL-based expiration and explicit invalidation.
Invalidation Reasons¶
| Reason | Trigger |
|---|---|
logout | Peer explicitly logs out |
reputation_drop | Peer reputation drops below minTrustScore |
repeated_failures | N consecutive tool execution failures |
manual_revoke | Owner manually revokes via CLI |
security_event | Automatic security event handler |
Security Event Handler¶
The SecurityEventHandler monitors peer behavior and automatically invalidates sessions when:
- A peer's reputation drops below the configured
minTrustScore - A peer exceeds the consecutive failure threshold
- A security event is triggered externally
CLI¶
lango p2p session list # List active sessions
lango p2p session revoke --peer-did <did> # Revoke specific peer
lango p2p session revoke-all # Revoke all sessions
Knowledge Firewall¶
The knowledge firewall enforces access control for peer queries. The default policy is deny-all -- explicit rules must be added to allow access.
ACL Rules¶
Each rule specifies:
| Field | Description |
|---|---|
peerDid | Peer DID this rule applies to ("*" for all peers) |
action | "allow" or "deny" |
tools | Tool name patterns (supports * wildcard, empty = all tools) |
rateLimit | Maximum requests per minute (0 = unlimited) |
Response Sanitization¶
All responses to peer queries are automatically sanitized:
- Absolute file paths are redacted
- Sensitive fields (passwords, tokens, private keys) are stripped
- Internal IDs and database paths are removed
ZK Attestation¶
When p2p.zkAttestation is enabled, responses include a zero-knowledge proof that the response was generated by the claimed agent without revealing internal state.
Approval Pipeline¶
Inbound P2P tool invocations pass through a three-stage gate before execution:
flowchart TD
A[Incoming Tool Request] --> B{Firewall ACL}
B -- Deny --> X[Reject]
B -- Allow --> C{Reputation Check}
C -- Below minTrustScore --> X
C -- Pass --> D{Owner Approval}
D -- Denied --> X
D -- Approved --> E[Execute Tool]
E --> F[Sanitize Response]
F --> G[Return Result]
D -. "Auto-approve shortcut" .-> E
style X fill:#ef4444,color:#fff
style E fill:#22c55e,color:#fff Stage 1: Firewall ACL¶
The Knowledge Firewall evaluates static allow/deny rules by peer DID and tool name pattern. Requests that don't match any allow rule are rejected immediately.
Stage 2: Reputation Check¶
If a reputation checker is configured, the peer's trust score is verified against minTrustScore (default: 0.3). New peers with no history (score = 0) are allowed through. Peers with a score above 0 but below the threshold are rejected.
Stage 3: Owner Approval¶
The local agent owner is prompted to approve or deny the tool invocation. This stage supports several auto-approval shortcuts:
| Condition | Behavior |
|---|---|
Paid tool, price < autoApproveBelow | Auto-approved if within spending limits (maxPerTx, maxDaily) |
autoApproveKnownPeers: true | Previously authenticated peers skip handshake approval |
| Free tool | Always requires interactive owner approval |
When auto-approval conditions are not met, the request falls back to the composite approval provider (Telegram inline keyboard, Discord button, Slack interactive message, or terminal prompt).
Tool Execution Sandbox¶
Inbound P2P tool invocations can run in an isolated sandbox to prevent malicious tool code from accessing process memory (passphrases, private keys, session tokens).
Isolation Modes¶
| Mode | Backend | Isolation Level | Overhead |
|---|---|---|---|
| Subprocess | os/exec | Process-level | ~10ms |
| Container | Docker SDK | Container-level (namespaces, cgroups) | ~50-100ms |
Container Runtime Probe Chain¶
When container mode is enabled, the executor probes available runtimes in order:
- Docker -- Full Docker SDK integration with OOM detection, label-based cleanup (
lango.sandbox=true) - gVisor -- Stub for future implementation
- Native -- Falls back to subprocess executor
Container Pool¶
An optional pre-warmed container pool reduces cold-start latency. Configure poolSize (default: 0 = disabled) and poolIdleTimeout (default: 5m).
Configuration¶
{
"p2p": {
"toolIsolation": {
"enabled": true,
"timeoutPerTool": "30s",
"maxMemoryMB": 512,
"container": {
"enabled": true,
"runtime": "auto",
"image": "lango-sandbox:latest",
"networkMode": "none",
"readOnlyRootfs": true,
"poolSize": 3
}
}
}
}
CLI¶
lango p2p sandbox status # Show sandbox runtime status
lango p2p sandbox test # Run smoke test
lango p2p sandbox cleanup # Remove orphaned containers
Discovery¶
Agent discovery uses GossipSub for decentralized agent card propagation:
- Each agent publishes its Agent Card periodically on the
/lango/agentcard/1.0.0topic - Cards include: name, description, DID, multiaddrs, capabilities, pricing, and ZK credentials
- Peers can search for agents by capability tag using
FindByCapability - ZK credentials on cards are verified before acceptance
Agent Card Structure¶
{
"name": "my-agent",
"description": "Research assistant",
"did": "did:lango:02abc...",
"multiaddrs": ["/ip4/1.2.3.4/tcp/9000"],
"capabilities": ["research", "code-review"],
"pricing": {
"currency": "USDC",
"perQuery": "0.01"
},
"peerId": "QmAbc..."
}
Credential Revocation¶
The gossip discovery system supports credential revocation to prevent compromised or retired agents from being discovered.
Revocation Mechanisms¶
RevokeDID(did)-- Adds a DID to the local revocation set. Revoked DIDs are rejected during agent card validation.IsRevoked(did)-- Checks whether a DID has been revoked.maxCredentialAge-- Credentials older than this duration (measured fromIssuedAt) are rejected even if not explicitly revoked.
Credential Validation¶
When processing incoming agent cards via GossipSub, three checks are applied:
- Expiration --
ExpiresAtmust be in the future - Staleness --
IssuedAt + maxCredentialAgemust be in the future - Revocation -- The agent's DID must not be in the revocation set
Configure maxCredentialAge in the ZKP settings:
{
"p2p": {
"zkp": {
"maxCredentialAge": "24h"
}
}
}
ZK Circuits¶
When ZK features are enabled, Lango uses four zero-knowledge circuits:
| Circuit | Purpose | Public Inputs |
|---|---|---|
| Identity | Prove DID ownership without revealing the private key | DID hash |
| Membership | Prove membership in an authorized peer set | Merkle root |
| Range | Prove a value falls within a range (e.g., reputation score) | Min, Max bounds |
| Attestation | Prove response authenticity with freshness guarantees | AgentID hash, MinTimestamp, MaxTimestamp |
| Capability | Prove authorized capability with agent binding | CapabilityHash, AgentTestBinding |
Attestation Freshness¶
The Attestation circuit includes MinTimestamp and MaxTimestamp public inputs with range assertions, ensuring proofs are fresh and cannot be replayed outside the validity window.
Structured Attestation Data¶
Attestation proofs are returned as structured AttestationData:
{
"proof": "<base64-encoded-proof>",
"publicInputs": ["<agent-id-hash>", "<min-ts>", "<max-ts>"],
"circuitId": "attestation",
"scheme": "plonk"
}
SRS Configuration¶
Configure the proving scheme and SRS (Structured Reference String) source:
| Setting | Values | Description |
|---|---|---|
p2p.zkp.provingScheme | "plonk", "groth16" | ZKP proving scheme |
p2p.zkp.srsMode | "unsafe", "file" | SRS generation mode |
p2p.zkp.srsPath | file path | Path to SRS file (when srsMode = "file") |
Production SRS
The "unsafe" SRS mode uses a deterministic setup suitable for development. For production deployments, use "file" mode with an SRS generated from a trusted ceremony.
Configuration¶
Settings:
lango settings→ P2P Network
{
"p2p": {
"enabled": true,
"listenAddrs": ["/ip4/0.0.0.0/tcp/9000"],
"bootstrapPeers": [],
"keyDir": "~/.lango/p2p",
"enableRelay": false,
"enableMdns": true,
"maxPeers": 50,
"handshakeTimeout": "30s",
"sessionTokenTtl": "1h",
"autoApproveKnownPeers": false,
"firewallRules": [
{
"peerDid": "*",
"action": "allow",
"tools": ["search_*"],
"rateLimit": 10
}
],
"gossipInterval": "30s",
"zkHandshake": false,
"zkAttestation": false,
"requireSignedChallenge": false,
"zkp": {
"proofCacheDir": "~/.lango/zkp",
"provingScheme": "plonk",
"srsMode": "unsafe",
"srsPath": "",
"maxCredentialAge": "24h"
},
"toolIsolation": {
"enabled": false,
"timeoutPerTool": "30s",
"maxMemoryMB": 512,
"container": {
"enabled": false,
"runtime": "auto",
"image": "lango-sandbox:latest",
"networkMode": "none",
"poolSize": 0
}
},
"workspace": {
"enabled": false,
"dataDir": "~/.lango/workspaces",
"maxWorkspaces": 10,
"maxBundleSizeBytes": 0,
"chroniclerEnabled": false,
"autoSandbox": false,
"contributionTracking": false
}
}
}
See the Configuration Reference for all P2P settings.
REST API¶
When the gateway is running (lango serve), read-only P2P endpoints are available for monitoring and external integrations:
| Endpoint | Description |
|---|---|
GET /api/p2p/status | Peer ID, listen addresses, connected peer count |
GET /api/p2p/peers | List of connected peers with multiaddresses |
GET /api/p2p/identity | Local DID (did:lango:...) and peer ID |
GET /api/p2p/reputation | Peer trust score and exchange history |
GET /api/p2p/pricing | Tool pricing (single or all tools) |
# Check node status
curl http://localhost:18789/api/p2p/status
# List connected peers
curl http://localhost:18789/api/p2p/peers
# Get DID identity
curl http://localhost:18789/api/p2p/identity
# Query peer reputation
curl "http://localhost:18789/api/p2p/reputation?peer_did=did:lango:02abc..."
# Get tool pricing
curl http://localhost:18789/api/p2p/pricing
curl "http://localhost:18789/api/p2p/pricing?tool=knowledge_search"
These endpoints query the running server's persistent P2P node. They are public (no authentication) and expose only node metadata. See the HTTP API Reference for response format details.
CLI Commands¶
The CLI commands create ephemeral P2P nodes for one-off operations, independent of the running server:
lango p2p status # Show node status
lango p2p peers # List connected peers
lango p2p connect <multiaddr> # Connect to a peer
lango p2p disconnect <peer-id> # Disconnect from a peer
lango p2p firewall list # List firewall rules
lango p2p firewall add # Add a firewall rule
lango p2p discover # Discover agents
lango p2p identity # Show local identity
lango p2p reputation --peer-did <did> # Query trust score
lango p2p pricing # Show tool pricing
lango p2p session list # List active sessions
lango p2p session revoke --peer-did <did> # Revoke peer session
lango p2p session revoke-all # Revoke all sessions
lango p2p sandbox status # Show sandbox status
lango p2p sandbox test # Run sandbox smoke test
lango p2p sandbox cleanup # Remove orphaned containers
lango p2p team list # List active P2P teams
lango p2p team status <id> # Show team details
lango p2p team disband <id> # Disband an active team
lango p2p zkp status # Show ZKP configuration
lango p2p zkp circuits # List ZKP circuits
lango p2p workspace create <name> # Create a collaborative workspace
lango p2p workspace list # List all workspaces
lango p2p workspace status <id> # Show workspace status and members
lango p2p workspace join <id> # Join a workspace
lango p2p workspace leave <id> # Leave a workspace
lango p2p git init <workspace-id> # Initialize workspace git repo
lango p2p git log <workspace-id> # Show workspace commit history
lango p2p git diff <workspace-id> <from> <to> # Show diff between commits
lango p2p git push <workspace-id> # Create and push git bundle
lango p2p git fetch <workspace-id> # Fetch and apply git bundle
See the P2P CLI Reference for detailed command documentation.
Paid Value Exchange¶
Lango supports paid P2P tool invocations via the Payment Gate. When pricing is enabled, remote peers must pay in USDC before invoking tools.
Payment Gate Flow¶
- Price Query — The caller queries the provider's pricing via
p2p_price_queryorGET /api/p2p/pricing - Price Quote — The provider returns a
PriceQuoteResultwith the tool price in USDC - Payment — The caller sends USDC via
p2p_payto the provider's wallet address - Tool Invocation — After payment confirmation, the caller invokes the tool via
p2p_query
Auto-Approval for Small Amounts¶
When payment.limits.autoApproveBelow is set, small payments are auto-approved without user confirmation. The auto-approval check evaluates three conditions:
- Threshold — the payment amount is strictly below
autoApproveBelow - Per-transaction limit — the amount does not exceed
maxPerTx - Daily limit — the cumulative daily spend (including this payment) does not exceed
maxDaily
If any condition fails, the system falls back to interactive approval via the configured channel (Telegram, Discord, Slack, or terminal).
This applies to both outbound payments (p2p_pay, payment_send) and inbound paid tool invocations where the owner's approval pipeline checks the tool price against the spending limiter.
USDC Registry¶
Payment settlements use on-chain USDC transfers. The system supports multiple chains via the contracts.LookupUSDC() registry. Wallet addresses are derived from peer DIDs.
Configuration¶
{
"p2p": {
"pricing": {
"enabled": true,
"perQuery": "0.10",
"toolPrices": {
"knowledge_search": "0.25",
"browser_navigate": "0.50"
}
}
}
}
Trust-Based Pricing Tiers¶
The pricing system supports differentiated payment flows based on peer reputation:
| Tier | Reputation Score | Payment Flow |
|---|---|---|
| PostPay | ≥ postPayMinScore (default: 0.8) | Tool executes first, payment settles after |
| PrePay | < postPayMinScore | Payment must confirm before tool execution |
This tiered approach rewards trusted peers with lower friction while protecting against unknown or low-reputation callers.
Configuration¶
{
"p2p": {
"pricing": {
"trustThresholds": {
"postPayMinScore": 0.8
}
}
}
}
Settlement Service¶
The settlement service handles asynchronous on-chain USDC settlement for P2P tool invocations. It supports EIP-3009 authorization-based transfers for gasless payments.
Settlement Flow¶
- Trigger — After a paid tool invocation completes (or before, for PrePay tier)
- Build transaction — Constructs an EIP-3009
transferWithAuthorizationcall - Submit with retry — Submits the transaction with exponential backoff (up to
maxRetries) - Wait for confirmation — Monitors on-chain receipt up to
receiptTimeout - Record outcome — Updates reputation (success/failure) via
ReputationRecorder
Subscriber Pattern¶
External components can subscribe to settlement outcomes:
service.Subscribe(func(result SettlementResult) {
// Handle completed settlement
})
Configuration¶
{
"p2p": {
"pricing": {
"settlement": {
"receiptTimeout": "2m",
"maxRetries": 3
}
}
}
}
| Key | Default | Description |
|---|---|---|
p2p.pricing.settlement.receiptTimeout | 2m | Max wait for on-chain receipt confirmation |
p2p.pricing.settlement.maxRetries | 3 | Max transaction submission retries |
Buyer Auto-Payment¶
The p2p_invoke_paid tool automates the complete paid remote tool invocation flow from the buyer side:
- Discover — Find the target agent via DHT or gossip
- Query price — Fetch the tool's USDC price from the provider
- Authorize payment — Sign an EIP-3009
transferWithAuthorizationfor the exact amount - Invoke tool — Send the tool request with the payment authorization attached
- Confirm settlement — Wait for on-chain confirmation
This tool is automatically available when both P2P and payment features are enabled. It integrates with the spending limiter (maxPerTx, maxDaily) and auto-approval thresholds.
P2P Team Coordination¶
P2P teams enable task-scoped collaboration between multiple agents across the network.
Team Lifecycle¶
stateDiagram-v2
[*] --> Forming
Forming --> Active: All members joined
Active --> Completed: Task finished
Active --> Disbanded: Leader disbands
Forming --> Disbanded: Timeout Roles¶
| Role | Description |
|---|---|
| Leader | Creates the team, assigns tasks, manages budget |
| Worker | Executes assigned subtasks |
| Reviewer | Validates task outputs |
| Observer | Monitors progress without active participation |
Member Status¶
Each member tracks their current status: Idle, Busy, Failed, or Left.
Scoped Context¶
Teams operate with a ScopedContext that controls metadata sharing between members. A ContextFilter restricts what information flows between agents, preventing unintended data leakage across organizational boundaries.
Budget Tracking¶
Teams track cumulative spending via AddSpend(). The leader manages the team's budget and can enforce spending limits across all members.
Conflict Resolution¶
When multiple team members produce conflicting results for the same task, the coordinator applies a configurable conflict resolution strategy:
| Strategy | Behavior |
|---|---|
trust_weighted | Picks the result from the highest-trust (fastest) agent |
majority_vote | Picks the most common result by simple majority |
leader_decides | Returns the first successful result for leader review |
fail_on_conflict | Returns an error if members produce different results |
Source: internal/p2p/team/conflict.go
Assignment Strategies¶
Task assignment to team members follows one of three strategies:
| Strategy | Behavior |
|---|---|
best_match | Assigns to the agent with the highest capability match |
round_robin | Cycles through members evenly |
load_balanced | Assigns to the least-busy member |
Source: internal/p2p/team/coordinator.go
Health Monitoring¶
The HealthMonitor periodically pings team members to detect unresponsive agents. It runs as a lifecycle component alongside the coordinator.
How it works:
- Every
interval(default: 30s), the monitor sends ahealth_pingto each non-leader member - If a member fails to respond, its consecutive miss counter increments
- When a member reaches
maxMissed(default: 3) consecutive failures, aTeamMemberUnhealthyEventis published - On task completion, all miss counters for the team are reset
- On team disband, tracking data is cleaned up to prevent memory leaks
| Config Field | Default | Description |
|---|---|---|
interval | 30s | Time between health check sweeps |
maxMissed | 3 | Consecutive missed pings before unhealthy |
Events:
| Event | Description |
|---|---|
team.member.unhealthy | Member missed maxMissed consecutive pings |
team.health.check | Aggregate health sweep completed (healthy/total counts) |
Source: internal/p2p/team/health_monitor.go
Graceful Shutdown¶
The GracefulShutdown method performs an ordered team shutdown sequence:
- Block new tasks -- Sets team status to
StatusShuttingDown, preventing new task delegation - Calculate settlement -- Counts active members with recorded spend for proportional budget settlement
- Persist state -- Saves the shutting-down status to the team store
- Publish event -- Publishes
TeamGracefulShutdownEventwithMembersSettledcount - Disband -- Calls
DisbandTeamto finalize cleanup
Graceful shutdown is triggered automatically by the team-shutdown bridge when a BudgetExhaustedEvent fires for the team's task ID. A TeamBudgetWarningEvent is also published when the budget crosses the 80% threshold.
| Event | Description |
|---|---|
team.graceful.shutdown | Team shut down with reason and settlement count |
team.budget.warning | Budget crossed 80% threshold (threshold, spent, budget) |
Source: internal/p2p/team/coordinator_shutdown.go
Git State Divergence Detection¶
The health monitor optionally collects git HEAD hashes from workspace members during health checks. When a GitStateProvider is configured, each successful ping is followed by a git state query.
DetectDivergence(workspaceID) compares collected HEAD hashes and identifies members whose HEAD differs from the majority:
- Count the frequency of each HEAD hash across all members
- Determine the majority HEAD (most common hash)
- Return a
GitDivergencefor each member whose HEAD differs
When divergence is detected, a WorkspaceGitDivergenceEvent is published with the majority HEAD and a list of divergent members.
| Event | Description |
|---|---|
workspace.git.divergence | Members have divergent git HEADs (majority vs divergent list) |
Source: internal/p2p/team/health_monitor.go, internal/eventbus/workspace_events.go
Payment Coordination¶
The PaymentCoordinator negotiates payment terms between team leader and members. Payment mode is selected based on trust score:
| Trust Score | Mode | Description |
|---|---|---|
| Price = 0 | free | No payment required |
| >= 0.7 | postpay | Tool executes first, payment settles after |
| < 0.7 | prepay | Payment must confirm before tool execution |
The Negotiator queries each member's tool price and trust score to determine the payment mode. Agreements include PricePerUse, Currency (USDC), MaxUses, and ValidUntil.
Source: internal/p2p/team/payment.go
Team Events¶
The event bus publishes team lifecycle events:
| Event | Description |
|---|---|
team.formed | New team created with leader and initial members |
team.disbanded | Team disbanded with reason |
team.member.joined | Agent joined a team with role |
team.member.left | Agent left a team with reason |
team.task.delegated | Task sent to team workers |
team.task.completed | Delegated task finished with success/failure counts |
team.conflict.detected | Conflicting results found from members |
team.payment.agreed | Payment terms negotiated with member |
team.health.check | Team-level health sweep completed |
team.member.unhealthy | Member missed consecutive health pings |
team.budget.warning | Team budget crossed 80% threshold |
team.graceful.shutdown | Team underwent graceful shutdown with settlement |
team.leader.changed | Team leader replaced |
Source: internal/eventbus/team_events.go
Collaborative Workspaces¶
Workspaces are collaborative environments where multiple agents share code, messages, and context within a P2P network. Each workspace has a lifecycle, members, and optional features like chronicling and contribution tracking.
Workspace Lifecycle¶
| Status | Description |
|---|---|
forming | Workspace created, waiting for members to join |
active | Workspace is active with participating agents |
archived | Workspace completed or disbanded |
Members and Roles¶
| Role | Description |
|---|---|
creator | Agent that created the workspace |
member | Agent that joined the workspace |
Message Types¶
Workspace messages facilitate real-time collaboration:
| Type | Description |
|---|---|
TASK_PROPOSAL | Propose a task for workspace members |
LOG_STREAM | Share log output in real-time |
COMMIT_SIGNAL | Signal that code has been committed |
KNOWLEDGE_SHARE | Share knowledge or context |
MEMBER_JOINED | A member joined the workspace |
MEMBER_LEFT | A member left the workspace |
GossipSub Topics¶
Workspaces use GossipSub for real-time message distribution: - Topic per workspace: /lango/workspace/<workspace-id> - Members subscribe on join, unsubscribe on leave
Chronicler¶
When chroniclerEnabled is true, workspace messages are persisted as graph triples for long-term knowledge retention. Each message generates triples: - workspace:message:<id> → type, workspace, sender, content, timestamp - Reply chains are linked via replyTo predicates
Source: internal/p2p/workspace/chronicler.go
Contribution Tracking¶
When contributionTracking is true, per-agent metrics are collected: - Commits: Number of git commits per member - Code Bytes: Total code contribution size - Messages: Number of workspace messages posted - Last Active: Most recent activity timestamp
Source: internal/p2p/workspace/contribution.go
Git Bundle Exchange¶
Git bundle exchange enables atomic code sharing between workspace members using bare git repositories and the git bundle protocol.
Architecture¶
- Bare Repo Store: Each workspace gets an isolated bare git repository at
<dataDir>/<workspaceID>/repo.git - Bundle Protocol: Uses
git bundle create/unbundlefor transfer (leverages git CLI for reliability) - P2P Transport: Bundles are transferred over the P2P network protocol
/lango/p2p-git/1.0.0
Bundle Workflow¶
- Init: Initialize a bare repo for a workspace (
Service.Init) - Create Bundle: Package all refs into a bundle (
Service.CreateBundle) → returns bytes + HEAD hash - Transfer: Send bundle over P2P to workspace members
- Apply Bundle: Receiver unbundles into their bare repo (
Service.ApplyBundle)
DAG Leaf Detection¶
Service.Leaves() identifies commits with no children — useful for detecting divergent branches and potential merge conflicts across distributed agents.
Configuration¶
| Field | Type | Default | Description |
|---|---|---|---|
maxBundleSizeBytes | int64 | 0 | Maximum bundle size (0 = unlimited) |
dataDir | string | ~/.lango/workspaces | Base directory for workspace repos |
Source: internal/p2p/gitbundle/
Agent Pool¶
The agent pool provides discovery, health monitoring, and intelligent selection of P2P agents.
Health Checking¶
The HealthChecker runs periodic probes against pooled agents:
| Status | Meaning |
|---|---|
Healthy | Agent responded within acceptable latency |
Degraded | Agent responding but with elevated latency or error rate |
Unhealthy | Agent unreachable or failing consistently |
Unknown | No health data available (newly discovered) |
Stale agents (no health check response within the eviction window) are automatically removed.
Weighted Selection¶
The Selector uses configurable weights to score agents:
- Reputation — Peer trust score from the reputation system
- Latency — Recent response times
- Success rate — Historical success/failure ratio
- Availability — Current health status
Selection strategies: - Select() — Pick the highest-scoring agent - SelectN(n) — Pick the top N agents - SelectRandom() — Weighted random selection - SelectBest() — Alias for Select()
Capability Search¶
FindByCapability(tag) returns all healthy agents that advertise a matching capability tag in their agent card.
Reputation System¶
The reputation system tracks peer behavior across exchanges and computes a trust score.
Trust Score Formula¶
score = successes / (successes + failures×2 + timeouts×1.5 + 1.0)
The score ranges from 0.0 to 1.0. The minTrustScore configuration (default: 0.3) sets the threshold for accepting requests from peers.
Exchange Tracking¶
Each peer interaction is recorded: - Success — Tool invocation completed normally - Failure — Tool invocation returned an error - Timeout — Tool invocation timed out
Querying Reputation¶
- CLI:
lango p2p reputation --peer-did <did> - Agent Tool:
p2p_reputationwithpeer_didparameter - REST API:
GET /api/p2p/reputation?peer_did=<did>
New peers with no reputation record are given the benefit of the doubt (trusted by default).
Reorg Protection¶
The on-chain escrow EventMonitor includes reorg protection to handle chain reorganizations on L2 networks.
Confirmation Depth¶
The monitor applies a confirmationDepth buffer (default: 2 blocks for Base L2) before processing events. Only blocks at latest - confirmationDepth or earlier are considered safe.
Block Hash Cache¶
A bounded blockHashes cache (max 256 entries) stores block hashes for continuity verification. Before processing new logs, the monitor checks that the parent block hash matches the cached value. A mismatch indicates a silent reorg within the confirmation window.
Reorg Detection¶
Two mechanisms detect reorganizations:
- Safe block regression -- If
safeBlock < lastBlock, the chain has reorganized. The monitor rolls back tosafeBlockand re-processes from that point. - Hash continuity check -- If the parent block's current hash differs from the cached hash, a silent reorg has occurred.
When a reorg is detected, an EscrowReorgDetectedEvent is published:
| Field | Description |
|---|---|
PreviousBlock | Last processed block before reorg |
NewBlock | New safe block after reorg |
Depth | Number of blocks reorganized |
ExceedsDepth | true if reorg depth exceeds confirmationDepth (critical) |
The on-chain escrow bridge subscribes to this event and logs at ERROR level for deep reorgs that exceed confirmation depth.
Configuration¶
| Key | Default | Description |
|---|---|---|
economy.escrow.onChain.confirmationDepth | 2 | Blocks to wait before processing events |
economy.escrow.onChain.pollInterval | 15s | Event monitor polling interval |
Source: internal/economy/escrow/hub/monitor.go
Event-Driven Bridges¶
The application layer wires P2P subsystems together through event-driven bridges. Each bridge subscribes to events from one subsystem and triggers actions in another, enabling decoupled cross-concern integration.
| Bridge | Source Events | Target Actions |
|---|---|---|
| On-Chain Escrow | EscrowOnChain* events, EscrowReorgDetectedEvent | Escrow engine state transitions (fund, activate, release, refund, dispute) |
| Team Budget | TeamFormed, TeamTaskDelegated, TeamTaskCompleted | Budget allocation, reservation, and spend recording |
| Team Escrow | TeamFormed, TeamTaskCompleted, TeamDisbanded | Escrow creation, milestone completion, release/refund on disband |
| Team Reputation | TeamMemberUnhealthy, TeamTaskCompleted, ReputationChanged | Record timeout/success, kick low-reputation members |
| Team Shutdown | BudgetAlert (>=80%), BudgetExhausted | Publish TeamBudgetWarningEvent, trigger GracefulShutdown |
| Workspace-Team | TeamFormed, TeamTaskCompleted, TeamDisbanded | Auto-create workspace, record contributions, unsubscribe gossip |
Source: internal/app/bridge_*.go
Owner Shield¶
The Owner Shield prevents owner PII from leaking through P2P responses. When configured, it sanitizes all outgoing P2P responses to remove:
- Owner name, email, and phone number
- Custom extra terms (e.g., company name, address)
- Conversation history (when
blockConversationsis true, which is the default)
Configuration¶
{
"p2p": {
"ownerProtection": {
"ownerName": "Alice",
"ownerEmail": "alice@example.com",
"ownerPhone": "+1234567890",
"extraTerms": ["Acme Corp"],
"blockConversations": true
}
}
}