docs: specify agent lineage model - #7003
Conversation
Signed-off-by: Bradley Axen <baxen@squareup.com>
🔐 Codex Security Review
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8536bdcf32
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
|
||
| ## Lineage query API | ||
|
|
||
| The relay exposes authenticated `GET /api/agents/{pubkey}/lineage`, scoped by the request to the current community. It returns known agents only; roots and unknown keys return 404. It must not return attestations, proof material, or secrets. |
There was a problem hiding this comment.
Model the lineage query as a Nostr event
Implementing this contract would introduce an endpoint-specific JSON API for ordinary agent metadata, despite the repository requiring new features to use Nostr events unless they genuinely need an HTTP-only surface; it also contradicts this specification's claim that lineage adds no new wire protocol. Define an event kind that clients can read through WebSocket or POST /query, or document why lineage uniquely requires HTTP.
AGENTS.md reference: AGENTS.md:L158-L163
Useful? React with 👍 / 👎.
|
|
||
| ## Compatibility and conflict handling | ||
|
|
||
| Existing directly owned agents migrate as root-to-agent edges with `root = parent` and depth 1. Missing historical attestation evidence must not be fabricated; such records are `legacy_unverified`. They retain existing own-auth compatibility. Before a legacy agent can sponsor a child, it must refresh with verified, matching, unconditional evidence. A refresh may supply evidence for the existing edge but cannot change parent, root, or depth. |
There was a problem hiding this comment.
Define migration for owners absent from the member roster
On open relays, existing code unconditionally backfills a verified NIP-OA owner even when that owner has no direct membership (crates/buzz-relay/src/api/bridge.rs:900-915 and crates/buzz-relay/src/config.rs:281-284). This migration requires every such projection to become a root-to-agent edge, but the root-admission rule and security invariant prohibit a non-member owner from anchoring that edge, so these valid existing agents cannot both migrate and retain the promised compatibility. Specify a legacy exception, quarantine state, or other migration outcome for this existing open-relay case.
Useful? React with 👍 / 👎.
Security notes on agent lineage(written by my agent) PR #7003 proposes nested agent lineage on top of NIP-OA and NIP-AA. It defines the contract before implementation, so it is useful to separate design gaps from security properties the implementation will need to preserve. What the proposal doesThe proposal turns the current owner-to-agent relationship into a short delegation chain: H is the root and client-visible manager. Each agent is the immediate parent of the next agent. The agent that signs an event remains its author; lineage does not rewrite authorship to H. The chain is limited to three agent levels. A and B may create children, while C may not. The depth limit bounds how far authority can be redelegated, but it does not limit how many children an agent can create. On an agent's first valid authentication, the relay verifies the NIP-OA signature from its immediate parent, resolves the parent's ancestry, derives the root and depth, and stores an immutable lineage record. Later authorization checks are expected to reject the agent if the agent, an ancestor, or the root has been disabled, banned, or otherwise lost the required admission. The root keeps the existing ability to manage its agents. Intermediate parents do not automatically receive the root's channel, repository, administrative, sibling, or human rights. The manager shown in the UI is always the verified human root, not the immediate parent and not an arbitrary signer found in user-controlled data. The proposal also requires one shared effective-principal resolver across WebSocket, HTTP, and Git entry points, migration of existing directly owned agents to depth one, and invalidation of active descendant sessions when their authority becomes invalid. The security model hiding inside the featureThis is a capability-delegation system, even if the product calls it lineage. A signed edge does more than describe a relationship: it can cause a relay to admit a new principal and can let that principal create another principal. That distinction matters because classic authorization systems separate the right to exercise authority from the right to delegate it. SPKI, for example, puts an explicit delegation bit in an authorization certificate and intersects authority as certificate chains are reduced. Macaroons similarly make delegated authority attenuate as caveats are added. A child should never emerge with authority that its parent did not have and was not allowed to pass on. The other relevant systems principle is complete mediation. Every request must be checked against current authority, including after a ban, membership change, or revocation. Checking only at login, or allowing one entry point to bypass the shared resolver, leaves stale authority alive. This is the same family of problem described by Saltzer and Schroeder's design principles for protection systems. Keeping the signing actor distinct from the administrative root is also a defense against the confused deputy problem. The relay must not perform an agent's operation using ambient authority inherited from the human merely because it can resolve that human as the agent's root. How this can go wrong1. An ordinary NIP-OA endorsement becomes delegation authorityToday, an owner signing a NIP-OA credential says that a particular public key is its agent, subject to the credential's conditions. The lineage proposal additionally treats an unconditional credential as permission for that agent to create more agents. Those are different powers. If H intended A to act for H but did not intend A to mint B, the proposal silently upgrades A's authority. A depth limit does not solve that problem; it limits the length of an authority chain after redelegation has already been allowed. This is a design problem, not merely an implementation warning. Delegation needs to be explicit in the signed grant. An unconditional event credential is not enough evidence that the owner intended redelegation. This is the classic authority amplification through unconstrained delegation problem. SPKI separates permission to exercise an authorization from permission to propagate it, and intersects authorization while reducing a certificate chain. Macaroons apply the same idea through attenuation: a delegated credential can become narrower, but not broader. Lineage should preserve that monotonic property. 2. Global lineage and community-local state are being mixed togetherNIP-OA and NIP-AA intentionally make an agent relationship portable. H should be able to authorize A once, and A should work at every relay where H is a member. Adding a new community should not require the human to approve A again. The PR currently says each community has its own lineage tree and requires an intermediate parent to be registered before its child can authenticate. That breaks portability for nested agents. If H, A, and B have a valid global chain, but only B contacts a new relay, B is rejected because A has not first logged in there. Operationally, that recreates per-community onboarding even though nobody clicks an approval dialog. The relationship can be global while enforcement remains local. B should present the complete signed chain Global portability does increase the impact of an agent-key or broker compromise. A stolen agent key plus its portable credential can be used at any relay where the root is a member. Compromising one relay alone does not provide that pivot: the attacker still needs the agent private key or a signing oracle to answer a fresh NIP-42 challenge. That blast radius appears to be an intentional NIP-AA tradeoff rather than an accidental escalation. It should be stated plainly and supported by revocation and secure key custody. The closest established model is portable certificate-path validation. In PKIX path validation, a credential chain is portable evidence, while the relying party supplies its own trust anchors, policy, time, and revocation inputs. Buzz can use the same separation: the NIP-OA lineage is global evidence, while each relay decides whether the root is currently admitted and what the resulting agent may do there. Local materialization is an optimization and enforcement record, not a reason to require the chain to have been replayed one edge at a time. 3. Child key creation can violate the post-IFC trust boundaryThe proposal says a creator or broker generates the child keypair. Those alternatives have very different security properties. In the secure information-flow design, the trusted broker holds agent keys and gives keyless, audience-bound agent instances semantic operations. An agent process should not receive its own reusable bearer key, another agent's key, or a general If parent agent A generates B's key and keeps a copy, A can impersonate B, bypass B's audience separation, and use B after B's runtime is supposedly disabled. The lineage record would remain cryptographically valid while the execution isolation it is meant to support has failed. Agent creation should therefore be a broker operation such as This is a key-confinement and information-flow control problem. Denning's original lattice model of secure information flow requires information and authority to move only through permitted flows. The secure agent information-flow design applies that principle by making agent instances keyless and audience-bound. Giving A the key for B creates an unmediated channel that sits outside the broker's labels, declassification checks, and revocation controls. 4. Revocation has to reach every entry point and every cacheThe proposal correctly requires invalidation when an actor, ancestor, or root is disabled. Implementing that only at WebSocket authentication would be insufficient. HTTP, Git, media, workflow, huddle, and any other owner-backed path must resolve the same current authority. Existing sessions, cached membership decisions, and long-running operations also matter. A failed disconnect is acceptable only if the next protected operation is denied by durable authorization. This is an implementation requirement, but it is security-critical: one forgotten fast path defeats branch revocation. Local per-agent bans are useful and do not conflict with global credentials. They mean "this relay rejects this agent," not "the human must authorize this agent separately for this relay." A global credential-level revocation mechanism is still needed if the owner wants to withdraw the relationship everywhere. This is the classic complete-mediation and stale-authorization problem. Saltzer and Schroeder require every access to be checked, not merely the first access in a session. Zanzibar shows the distributed-systems side of the same issue: authorization changes and protected operations need an explicit consistency relationship so a stale replica or cache does not resurrect removed access. Buzz does not need Zanzibar's architecture, but it does need to define the ordering guarantee between a ban or revocation and subsequent requests. 5. Depth does not control fan-out or resource consumptionA depth-three tree can still contain millions of siblings. The PR acknowledges this and says existing quotas and accounting must not reset for each child. That is the right contract, but it needs a concrete aggregation key. Work performed by A, B, and C should remain chargeable to the root or another intentionally chosen budget domain. Otherwise nested identities become a cheap way to bypass rate limits, storage limits, concurrent-session limits, and abuse controls. Whether the product also limits children per parent is a policy choice. Ensuring that new children do not obtain fresh resource budgets is a security requirement. This is a form of the Sybil problem described in Douceur's The Sybil Attack: one principal can cheaply present many identities and defeat controls that assume identities represent independent resource owners. Lineage does not need to prevent a root from creating many agents, but rate limits and quotas must recognize that those agents share an accountable root. 6. Global uniqueness, conflicts, and key recovery are underspecifiedThe PR makes parent, root, and depth immutable, which is useful for audit meaning. It does not explain what happens when the same child key has valid endorsements from two roots, or when different relays first observe different chains. "First valid chain seen" can produce permanently different lineage on different relays. Signatures prove that each issuer made a statement; they do not select one globally canonical statement. If one true global lineage is required, the protocol needs an explicit conflict rule and a consistency mechanism. If lineage is allowed to be interpreted locally, the design should say so and avoid claiming global uniqueness. Immutability also needs a recovery story. Human and agent keys will be rotated or compromised. A system that can only disable a tree and recreate every descendant may be acceptable, but that operational cost should be deliberate rather than discovered during an incident. Competing valid chains are an equivocation and fork-consistency problem. A signature authenticates each statement but does not stop a signer from making two contradictory statements or two relays from accepting different first-seen histories. SUNDR formalizes the related property of fork consistency: once clients have been shown divergent histories, reconciling them requires a protocol stronger than independently verifying signatures. Buzz needs either a canonical resolution mechanism or an explicit decision that lineage is relay-local when valid chains conflict. 7. The legacy migration rule conflicts with root admissionThe proposal says existing owned agents migrate to Those records cannot both migrate automatically and immediately satisfy the new invariant. They need an explicit compatibility state: for example, retain existing direct-agent authentication, mark the lineage This is a secure migration and fail-safe-defaults problem. Saltzer and Schroeder's fail-safe defaults say that access should follow an explicit grant rather than the absence of contrary evidence. Grandfathering an old relationship for its existing behavior can be reasonable; treating missing historical proof as a new right to delegate would turn compatibility ambiguity into additional authority. 8. The lineage query does not need a special HTTP APIThe proposed This is primarily an architectural callout, not a cryptographic weakness. It becomes a security issue if the extra endpoint develops authorization behavior that differs from the shared event path. The established failure mode is reference-monitor fragmentation. Complete mediation depends on a small, consistently invoked authorization mechanism. Every special-purpose entry point creates another place where authentication, community scoping, bans, or cache invalidation can drift. Saltzer and Schroeder's economy of mechanism and complete mediation support keeping this query on Buzz's existing Nostr authorization path unless HTTP provides a necessary property that path cannot supply. Which findings are problems, and which are guardrails?The following are problems that should change the design or current implementation:
The following are implementation guardrails. The design already points in the right direction, but correctness depends on getting them right everywhere:
The following are product policy choices rather than vulnerabilities by themselves:
A design I think matches the product intentKeep NIP-OA's owner-to-agent relationship global. A human authorizes an agent once; a new community does not require another human approval. Communities retain local control over membership, resource permissions, bans, and resource accounting. Represent nested lineage as a globally portable signed delegation chain. Each edge needs to bind at least: The exact encoding can be an extension to NIP-OA or a separate lineage-specific credential, but the signed data must distinguish "this key may act as my agent" from "this key may create further agents." The latter must be explicit. A child receives only the intersection of the authority its parent holds, the authority the parent is permitted to delegate, and the restrictions on the new edge. On first contact with a relay, the agent presents its complete chain. The relay treats the chain as evidence, verifies every signature and restriction, confirms that the root currently qualifies at this relay, rejects disabled or revoked identities, and atomically materializes the chain locally. Missing intermediate local records do not force the agents or human through an onboarding sequence. The relay then applies local policy. It may ban one agent or an entire branch, aggregate usage to the root, and deny access to community resources that the actor has not been granted. None of those controls require changing the globally signed relationship or asking the human to add the same agent again. Agent keys stay in the trusted broker. A parent agent invokes a closed Finally, make revocation a first-class signed object with stable credential identifiers. Relays should enforce local bans immediately and consume global revocations as they arrive. The design must state the expected consistency during propagation: a decentralized system cannot promise instantaneous global revocation unless all admission paths consult a shared, available authority. This preserves the UX goal—authorize an agent once and use it wherever its human is admitted—while making delegation intentional, keeping agent keys inside the IFC boundary, and letting each community enforce its own current security policy. |
An explicitly delegated, globally portable lineage credential(My agent wrote this.) My preference is not to overload unchanged NIP-OA. Keep existing NIP-OA as the global direct-agent credential, and add a domain-separated, globally portable lineage credential with explicit redelegation. The current NIP-OA signature covers only This matters because the proposed inference is stronger than the existing signature: I think the relay should instead require: This is the distinction SPKI makes between permission to exercise authority and permission to propagate it. Macaroons express the same property as attenuation: delegated authority may become narrower, but not broader. Why I think this is saferIt contains compromise. Under the current proposal, compromising any unconditionally authorized agent also gives the attacker agent-creation authority beneath it. The attacker can create new identities because delegation is automatic. With an explicit grant, compromising A still lets the attacker act as A, but it does not let them expand into new principals unless H granted A that power. if grant.remaining_delegations == 0 {
reject_create_child();
}It does not reinterpret old signatures. Existing NIP-OA credentials were signed when “unconditional” meant “no event conditions.” It did not mean “may create descendants.” A new domain-separated credential proves that the human authorized delegation under the new semantics. It preserves global portability. The current proposal reconstructs lineage from community-local state, so B cannot authenticate until A has already registered in that community. With a portable chain, B arrives with It fits the post-IFC broker. The broker can expose a narrow The cost is another credential format, larger authentication proofs, a new interoperability contract, and two compatibility paths. If the intended policy is genuinely “every unconditional agent may always create arbitrary children,” the current proposal is simpler. But that should be treated as intentionally granting every agent a powerful ambient capability, not as something NIP-OA already proves. The short version is: the current proposal documents delegation as an interpretation; this design makes delegation cryptographically explicit. Related prior art: PicocertPicocert is a useful compact example of the certificate-chain shape: a caller supplies a chain, validation walks it to an independently selected trust anchor, and leaf proof-of-possession is checked separately (chain validation). That supports the general idea that an agent can carry portable chain evidence while the relay chooses the trusted root locally. Picocert serves a different purpose: it is a minimal identity-certificate library, not an authorization-delegation format. It is prior art for the chain-verification flow, not a proposed dependency or wire format. The lineage credential still needs Nostr-native keys plus signed delegation and depth constraints of its own. The signed agent grantLogically, the new credential could look like this: struct SignedAgentGrant {
body: AgentGrant,
signature: SchnorrSignature,
}
struct AgentGrant {
version: u8,
/// H for H->A, A for A->B, and so on.
issuer: PublicKey,
/// Agent receiving this grant.
subject: PublicKey,
/// Previous edge in the lineage. None only for H->A.
parent_grant: Option<GrantId>,
/// Number of additional agent generations the subject may create.
///
/// H->A: 2
/// A->B: 1
/// B->C: 0
remaining_delegations: u8,
/// Hard validity interval evaluated against relay time.
valid_from: Timestamp,
valid_to: Timestamp,
/// Makes otherwise-identical independently issued grants distinct.
nonce: [u8; 16],
}
type GrantId = Sha256;Sign it under a new domain: The grant deliberately has no
Nostr representation and compatibilityGiven Buzz's architecture, I would encode this as a new signed Nostr event rather than introduce an unrelated signature container: The Nostr event gives us a canonical ID and signature using the keys Buzz already has. The verifier validates the complete chain against a root selected from local membership state, then separately verifies the leaf key proof through NIP-42 or NIP-98. At admission, the relay accepts either the existing direct credential or the new lineage proof: enum PresentedAgentCredential {
/// Existing NIP-OA behavior. Direct H->A only and non-delegable.
NipOaV1(NipOaAuthTag),
/// New globally portable, explicitly delegated lineage behavior.
LineageV1(AgentLineageProof),
}
struct AgentLineageProof {
/// Ordered root-to-leaf.
grants: Vec<SignedAgentGrant>,
}Existing NIP-OA agents continue to work unchanged. An existing agent that needs to create children asks its human owner for the new explicit grant. Both credential types converge on the same internal principal after verification; this should not become two authorization systems. The exact NIP-42 transport can be specified separately. The leaf's AUTH signature must cover either the exact chain or a canonical digest of its grant IDs so that the selected lineage cannot be swapped independently of the proof-of-possession. At the maximum depth, the proof contains only three small signed events. Chain verificationThe implementation should separate untrusted input from a verified domain object: struct UntrustedLineageProof(Vec<Event>);
struct VerifiedLineage {
actor: PublicKey,
parent: PublicKey,
root: PublicKey,
depth: u8,
remaining_delegations: u8,
grant_ids: Vec<GrantId>,
valid_from: Timestamp,
valid_to: Timestamp,
}Only the chain verifier constructs fn verify_lineage(
proof: UntrustedLineageProof,
authenticating_actor: PublicKey,
now: Timestamp,
revocations: &RevocationSet,
) -> Result<VerifiedLineage> {
require!(!proof.is_empty());
require!(proof.len() <= 3);
let mut previous: Option<VerifiedGrant> = None;
let mut seen_keys = HashSet::new();
let mut verified_grants = Vec::new();
for raw_event in proof.0 {
let grant = parse_agent_grant(raw_event)?;
verify_nostr_id_and_signature(&grant.event)?;
require!(grant.issuer != grant.subject);
require!(!revocations.contains(grant.id));
require!(grant.valid_from <= grant.valid_to);
require!(grant.valid_from <= now);
require!(now < grant.valid_to);
match previous.as_ref() {
None => {
require!(grant.parent_grant.is_none());
require!(seen_keys.insert(grant.issuer));
}
Some(parent) => {
require!(grant.issuer == parent.subject);
require!(grant.parent_grant == Some(parent.id));
require!(parent.remaining_delegations > 0);
require!(
grant.remaining_delegations
<= parent.remaining_delegations - 1
);
// A child cannot outlive or predate its parent's authority.
require!(grant.valid_from >= parent.valid_from);
require!(grant.valid_to <= parent.valid_to);
}
}
require!(seen_keys.insert(grant.subject));
previous = Some(grant.clone());
verified_grants.push(grant);
}
let leaf = previous.ok_or(EmptyChain)?;
require!(leaf.subject == authenticating_actor);
Ok(derive_verified_lineage(verified_grants))
}For An ordinary legacy NIP-OA credential behaves like Relay admissionThe relay should classify the principal before taking a direct-member shortcut. A previously registered agent must not become a root merely because its key later appears in the member table. async fn authenticate(
community: CommunityId,
auth_event: Nip42Event,
credential: Option<PresentedAgentCredential>,
) -> Result<SessionPrincipal> {
verify_nip42_event(&auth_event)?;
if let Some(lineage) =
db.find_agent_lineage(community, auth_event.pubkey).await?
{
return revalidate_existing_agent(lineage, credential).await;
}
if db.is_trusted_root(community, auth_event.pubkey).await? {
return Ok(SessionPrincipal::Human {
actor: auth_event.pubkey,
});
}
let proof = require_lineage_credential(credential)?;
let verified = verify_lineage(
proof,
auth_event.pubkey,
clock.now(),
&db.revocations(community).await?,
)?;
db.transaction(|tx| async {
require!(
tx.is_active_trusted_root(community, verified.root).await?
);
require!(
!tx.is_known_agent(community, verified.root).await?
);
require!(
tx.all_lineage_members_enabled(
community,
&verified.grant_ids,
).await?
);
tx.pin_or_match_lineage(community, &verified).await?;
tx.materialize_missing_edges(community, &verified).await?;
Ok(())
}).await?;
Ok(SessionPrincipal::Agent {
actor: verified.actor,
root: verified.root,
chain: verified.grant_ids,
remaining_delegations: verified.remaining_delegations,
})
}
Community-local stateA globally portable credential does not require globally shared database rows. Each community stores its own projection and enforcement state: struct AgentLineageRow {
community_id: CommunityId,
agent_pubkey: PublicKey,
parent_pubkey: PublicKey,
root_pubkey: PublicKey,
leaf_grant_id: GrantId,
depth: u8,
remaining_delegations: u8,
status: AgentStatus,
authorization_revision: u64,
}
enum AgentStatus {
Active,
Disabled,
LegacyUnverified,
}
struct StoredLineageGrant {
community_id: CommunityId,
grant_id: GrantId,
issuer: PublicKey,
subject: PublicKey,
parent_grant_id: Option<GrantId>,
remaining_delegations: u8,
valid_from: Timestamp,
valid_to: Timestamp,
raw_signed_event: Event,
}The relationship is globally reusable. Each community independently materializes it, bans it, checks current root admission, and applies its own resource policy. Keep actor and root separateI would avoid an enum SessionPrincipal {
Human {
actor: PublicKey,
},
Agent {
actor: PublicKey,
root: PublicKey,
chain: Vec<GrantId>,
remaining_delegations: u8,
},
}Authorization then remains explicit: fn authorize(
principal: &SessionPrincipal,
action: Action,
resource: Resource,
) -> Result<()> {
revalidate_current_authority(principal)?;
match action {
Action::ReadChannel | Action::WriteChannel => {
// The agent needs its own resource permission.
require!(resource.allows(principal.actor()));
}
Action::ManageAgent { target } => {
// Administrative ownership belongs to the human root.
require!(principal.actor() == target.root);
}
Action::CreateChild { requested_hops } => {
let agent = require_agent(principal)?;
require!(agent.remaining_delegations > 0);
require!(requested_hops < agent.remaining_delegations);
}
}
Ok(())
}This avoids the confused-deputy version of the design: “A belongs to H, therefore execute A's request with H's privileges.” Broker-side creationIn the post-IFC system, child creation is a semantic broker operation: async fn create_child(
parent: AgentSession,
request: CreateChildRequest,
) -> Result<ChildHandle> {
require!(parent.audience == request.audience);
require!(parent.remaining_delegations > 0);
require!(
request.remaining_delegations
< parent.remaining_delegations
);
enforce_root_quota(parent.root)?;
enforce_parent_rate_limit(parent.actor)?;
// Non-exportable: the parent process never sees this key.
let child_key = broker.generate_agent_key()?;
let grant = broker.sign_agent_grant(
signing_as = parent.actor,
subject = child_key.public_key(),
parent_grant = parent.leaf_grant_id,
remaining_delegations = request.remaining_delegations,
validity = attenuate_validity(
parent.validity,
request.validity,
),
)?;
let runtime = broker.spawn_audience_bound_runtime(
child_key,
request.audience,
request.spec,
)?;
Ok(ChildHandle {
pubkey: child_key.public_key(),
lineage: parent.lineage.append(grant),
runtime,
// Deliberately no private key.
})
}A compromised A can request children only within A's signed authority and quota. It cannot extract B's key or turn B into a general-purpose signing oracle. RevocationA portable targeted revocation could identify the exact grant: struct AgentGrantRevocation {
grant_id: GrantId,
revoked_at: Timestamp,
reason: Option<String>,
signer: PublicKey,
signature: SchnorrSignature,
}The edge issuer and the root could both be permitted to revoke a branch. Local relay bans remain immediate and independent. The structure alone does not solve global revocation distribution. A relay validating an offline chain cannot prove that no revocation exists. We still need to choose among eventual propagation of signed revocation events, an online broker/status check, short-lived broker-renewed grants, or some combination. The post-IFC managed path makes short-lived grants practical; unmanaged agents will likely retain eventual or relay-local revocation semantics. Minimum coherent version
This keeps the intended UX—authorize an agent relationship once and use it wherever the human is admitted—without silently upgrading every existing NIP-OA credential into redelegation authority. |
Signed-off-by: Bradley Axen <baxen@squareup.com>
|
Would prefer to use NIP-OA all the way down for this and just have the relay do additional up-tree lookups if the owner has their own valud NIP-OA in their kind:0 until we find a human with a NIP-OA in the profile |
Summary
Replace the first-pass lineage specification with a narrowly scoped portable-grant design for review. This PR changes only
docs/specs/agent-lineage.md; it does not implement the revised protocol.The normative text is authored by the orchestrator. It separates signed, community-independent provenance from community-local admission and authorization, uses a bounded chain of versioned Nostr grants, and retains the human-root manager model.
Decisions for review
Sequence and status
The earlier uncommitted prototype exists in a separate worktree and implements the superseded NIP-OA/stored-ancestry approach. It has not been converted by this PR. Local startup/debugging changes and temporary diagnostics are not included here.