Skip to content

feat(identity): Phase 1+2 - connect() API, identity bridge, cache TTL, presence#288

Merged
nebukaga merged 31 commits intodevelopfrom
feature/identity_cp2
Mar 3, 2026
Merged

feat(identity): Phase 1+2 - connect() API, identity bridge, cache TTL, presence#288
nebukaga merged 31 commits intodevelopfrom
feature/identity_cp2

Conversation

@zomux
Copy link
Copy Markdown
Contributor

@zomux zomux commented Feb 17, 2026

Summary

Phase 1

  • connect() one-liner API for agent identity registration
  • connect_sync() synchronous wrapper
  • AgentIdentity dataclass with credential caching at ~/.openagents/agents/
  • Identity bridge in register_agent() — auto-registers agents in global registry
  • NetworkConfig identity fields (enabled, endpoint, api_key, auto_register, origin)

Phase 2

  • Cache TTL: cache_ttl parameter in connect() skips verify-key API call when cache is fresh
  • NetworkConfig: identity_cache_ttl field (default 3600s)
  • Discovery bug fix: get_connected_agents()get_agent_registry() in discovery_connector
  • Presence reporting: Heartbeat payload now includes agent names for presence tracking
  • Docs: docs/guides/identity_quickstart.md with SDK, API, and network auto paths

Test plan

  • connect() successfully registers agent via live API
  • Cache verification works on reconnect
  • _is_cache_fresh() correctly handles fresh/stale/missing cache
  • NetworkConfig.identity_cache_ttl defaults to 3600
  • Discovery connector heartbeat sends agent names (requires live network)

🤖 Generated with Claude Code

- Add connect() one-liner for agent identity registration
- Add identity config fields to NetworkConfig (identity_enabled,
  identity_endpoint, identity_api_key, identity_auto_register,
  identity_origin)
- Wire register_agent() to call remote registry when identity
  is enabled (graceful degradation on failure)
- Export connect, connect_sync, AgentIdentity from package root
Copilot AI review requested due to automatic review settings February 17, 2026 23:19
@vercel
Copy link
Copy Markdown

vercel bot commented Feb 17, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
openagents-studio Ready Ready Preview, Comment Mar 3, 2026 11:09pm

Request Review

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces Phase 1 of the agent identity system, providing a simplified one-liner API for connecting agents to the OpenAgents identity registry. The changes enable agents to register with a global identity service and optionally auto-register through network configuration.

Changes:

  • New connect() API for agent identity registration with local credential caching
  • Network configuration fields for identity service integration
  • Bridge between local network registration and remote identity registry

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 16 comments.

Show a summary per file
File Description
src/openagents/connect.py New module providing connect() and connect_sync() APIs with credential caching
src/openagents/init.py Exports connect functions and AgentIdentity at package root
src/openagents/models/network_config.py Adds 5 identity-related configuration fields (endpoint, api_key, etc.)
src/openagents/core/network.py Integrates remote identity registration into register_agent() flow
src/openagents/core/agent_identity.py Updates validate_agent() docstring to clarify responsibility

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.


__version__ = "0.8.5.post5"

from openagents.connect import AgentIdentity, connect, connect_sync
Copy link

Copilot AI Feb 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR description includes a test plan, but all test items are marked as incomplete (unchecked). Before merging, ensure that all test scenarios are verified: 1) verify from openagents import connect works, 2) test connect() with valid API key registers agent, 3) test connect() with cached credentials verifies without re-registering, 4) test network registration with identity_enabled=true auto-registers agents, 5) test network registration with identity failure doesn't block local registration.

Copilot uses AI. Check for mistakes.
Comment on lines +31 to +41
class AgentIdentity:
"""Represents a connected agent's identity."""

name: str
api_key: str # Agent-scoped key (oa_agentid_xxx)
profile_url: Optional[str] = None
did: Optional[str] = None
cert_serial: Optional[str] = None
origin: Optional[str] = None
created_at: Optional[str] = None

Copy link

Copilot AI Feb 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The AgentIdentity class in connect.py has the same name as the AgentIdentity class in core/agent_identity.py but represents a completely different concept. The connect.py version represents a remote identity registration result, while agent_identity.py represents local agent identity. This name collision can cause confusion and import issues. Consider renaming one of these classes, such as RemoteAgentIdentity or AgentRegistrationResult for the connect.py version.

Copilot uses AI. Check for mistakes.
@@ -1,3 +1,5 @@
"""OpenAgents - AI Agent Networks for Open Collaboration."""

__version__ = "0.8.5.post5"
Copy link

Copilot AI Feb 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The version in init.py is "0.8.5.post5" but pyproject.toml shows "0.8.5.post6". This version mismatch can cause confusion and packaging issues. Ensure both files have the same version number, or if init.py should be updated separately, add a comment explaining the versioning strategy.

Suggested change
__version__ = "0.8.5.post5"
__version__ = "0.8.5.post6"

Copilot uses AI. Check for mistakes.
Comment on lines +113 to +114
endpoint = endpoint.rstrip("/")

Copy link

Copilot AI Feb 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The endpoint rstrip("/") operation is duplicated in both the connect() function and on line 113. The endpoint should only be normalized once at the beginning of the function to avoid redundancy and potential confusion.

Suggested change
endpoint = endpoint.rstrip("/")

Copilot uses AI. Check for mistakes.
try:
from openagents.connect import connect as identity_connect
identity_result = await identity_connect(
name=agent_id,
Copy link

Copilot AI Feb 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The agent_id is passed as the 'name' parameter to identity_connect(), but the network's agent_id format may not match the naming requirements described in connect()'s docstring (globally unique, lowercase alphanumeric + hyphens). If agent_id can contain characters not allowed in the identity registry, this call will fail. Consider validating agent_id format before calling identity_connect() or transforming it to a compatible format.

Copilot uses AI. Check for mistakes.
logger.info(f"Remote identity registered for {agent_id}: {identity_result.profile_url}")
except Exception as e:
# Graceful degradation: never block local registration
logger.warning(f"Remote identity registration failed for {agent_id}: {e}")
Copy link

Copilot AI Feb 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a potential race condition if an agent manually calls connect() to register with the identity service and then registers with a network that has identity_auto_register=True. The network will attempt to register the same agent name and receive a 409 conflict error. While the error is caught and logged as a warning (graceful degradation), it might be better to check if the agent already has identity credentials before attempting registration, or to handle 409 errors specifically to avoid unnecessary warning logs.

Suggested change
logger.warning(f"Remote identity registration failed for {agent_id}: {e}")
status_code = getattr(e, "status_code", None)
if status_code is None:
response = getattr(e, "response", None)
if response is not None:
status_code = getattr(response, "status_code", None)
# Treat HTTP 409 Conflict (already registered) as a benign condition
if status_code == 409 or "409" in str(e) or "Conflict" in str(e):
logger.info(
f"Remote identity already registered or conflict for {agent_id}: {e}"
)
else:
logger.warning(f"Remote identity registration failed for {agent_id}: {e}")

Copilot uses AI. Check for mistakes.
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(json.dumps(data, indent=2, default=str))
try:
path.chmod(0o600)
Copy link

Copilot AI Feb 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

File permissions should be set using octal literal 0o600 immediately after opening the file descriptor, but this implementation creates the file, writes to it, and only then attempts to chmod. This creates a race condition where the file could be read by other users before permissions are restricted. Additionally, silently catching OSError can mask permission issues on systems that support chmod. Consider logging a warning or using more specific exception handling.

Copilot uses AI. Check for mistakes.
"cert_serial": cert_serial,
"origin": origin,
"created_at": created_at,
"account_api_key_prefix": api_key[:10] + "...",
Copy link

Copilot AI Feb 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The account_api_key_prefix field in the cache stores a prefix of the API key (first 10 characters). This could expose sensitive information if the cache file permissions are compromised. While this appears to be for debugging purposes, consider whether this field is necessary in the cache, or if it should be logged instead of stored.

Suggested change
"account_api_key_prefix": api_key[:10] + "...",

Copilot uses AI. Check for mistakes.
Comment on lines +178 to +179
f"cached credentials exist. Use a different name or ensure "
f"you have the correct API key."
Copy link

Copilot AI Feb 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error message for a 409 conflict suggests the user may have the wrong API key, but this could be misleading. A 409 typically means the agent name is already taken by a different account. The error message should clarify that the agent name is unavailable and suggest choosing a different name, rather than implying the API key might be wrong.

Suggested change
f"cached credentials exist. Use a different name or ensure "
f"you have the correct API key."
f"cached credentials exist. This usually means the name is "
f"already taken by a different account. Please choose a "
f"different agent name."

Copilot uses AI. Check for mistakes.
)
identity_api_key: Optional[str] = Field(
default=None,
description="Account API key (oa-xxxxx) for auto-registering agents in the global registry",
Copy link

Copilot AI Feb 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The identity_api_key field stores a sensitive API key in the network configuration without any indication of encryption or secure storage. If this configuration is persisted to disk (e.g., in network.yaml), the API key will be stored in plaintext. Consider adding a warning in the field description about securing this value, or implementing secure storage for sensitive configuration values.

Suggested change
description="Account API key (oa-xxxxx) for auto-registering agents in the global registry",
description=(
"Account API key (oa-xxxxx) for auto-registering agents in the global registry. "
"This is a sensitive secret; if set directly in configuration files (e.g., network.yaml), "
"it may be stored in plaintext. Prefer supplying this value via environment variables or a "
"secure secret store and avoid committing it to version control."
),

Copilot uses AI. Check for mistakes.
- Add cache_ttl parameter to connect() to skip verify-key when cache is fresh
- Add identity_cache_ttl field to NetworkConfig (default 3600s)
- Pass cache_ttl from NetworkConfig into identity bridge call
- Fix discovery_connector bug: get_connected_agents() -> get_agent_registry()
- Include agent names in heartbeat payload for presence tracking
- Add identity quickstart guide (SDK, API, network auto paths)
- Update project tracker with Phase 2 completion
@zomux zomux changed the title feat(agentid): Phase 1 - connect() API and identity bridge feat(identity): Phase 1+2 - connect() API, identity bridge, cache TTL, presence Feb 18, 2026
- EventGateway: per-agent activity counters (messages_sent, messages_received)
- Network: agent uptime tracking via _connect_time metadata + get_agent_uptimes()
- Discovery connector: reports activity data in heartbeat payload
- Project tracker updated with Phase 3 completion
Agent Workspace design doc covers full concept: workspace CRUD,
sessions, messaging, agent presence, adapters, identity, SKILL.md,
multi-agent collaboration, and 4-phase task breakdown.
…laude adapter, CLI commands

- workspace_client.py: API client, identity storage (~/.openagents/identity.json), agent name generation
- mcp_server.py: MCP server exposing workspace tools (send_message, get_history, get_agents, status)
- adapters/claude.py: Claude Code adapter with adaptive polling, heartbeat loop, Claude Agent SDK integration
- CLI: `openagents connect claude`, `login`, `rename`, `mcp-server` commands
- 11 unit tests for workspace SDK modules
When agent already exists, the 409 response returns data=null. The
previous code used result.get("data", {}).get("api_key") which fails
because data key exists with value None. Use (result.get("data") or {})
to correctly fall through to the default dict.
… streaming

- Unset CLAUDECODE env var in ClaudeAgentOptions.env to allow the
  adapter to spawn Claude Code from within an existing session
- Handle MessageParseError for unknown events like rate_limit_event
  instead of crashing the entire handler
- Track processed message IDs to prevent reprocessing on retries
- Add 30s backoff when rate limited
- Stream intermediate tool use steps as status messages to the
  workspace so users can see what the agent is doing
- Add retry loop with exponential backoff (15s/30s/60s) for rate limits
- Only retry when agent did no work; skip retry if tools were used or
  response was already sent (prevents duplicate messages)
- Track conversation_id for continue_conversation/resume across messages
- Track processed message IDs to prevent reprocessing after rate limit
- Structured fallback messages based on what work was completed
Replace claude-agent-sdk with direct `claude` CLI subprocess using
stream-json output. This eliminates the SDK's MessageParseError crash
on rate_limit_event messages — the CLI handles rate limits internally.

Key changes:
- Spawn `claude -p --output-format stream-json --verbose` subprocess
- Parse JSON lines directly, skip unknown event types gracefully
- rate_limit_event is now just a debug log, not a fatal error
- Session continuity via --resume with saved session_id
- Removed retry/backoff machinery (no longer needed)
- No more claude-agent-sdk dependency for the adapter

Tested: two-message conversation with tool use (Write + Bash),
rate_limit_event handled, session resume works, no duplicates.
The adapter now uses the claude CLI directly via subprocess,
so the Python SDK package is no longer needed.
When the backend returns {"data": null} (e.g., 404 for undeployed
endpoints), `data.get("data", data)` returns None, causing
"argument of type 'NoneType' is not iterable" on the `in` check.
Use `data.get("data") or data` and isinstance guards instead.
All three connect commands (claude, codex, openclaw) now work without
an API key. Registration uses origin="cli" and the backend allows
anonymous registration for zero-friction onboarding.
When an adapter receives the first message in a session, it generates
a short title from the message content (heuristic, no LLM call) and
updates the session via PATCH if the title is still the default
"Session N" pattern. All three adapters (Claude, Codex, OpenClaw)
share the logic via adapters/utils.py.

Also adds get_session() and update_session() to WorkspaceClient.
Phase 2 (multi-agent collaboration) all complete. Added Phase 2.5
tracking UX polish: landing page redesign, anonymous onboarding,
session auto-naming. Codex and OpenClaw adapters moved from Phase 3.
- Add slug field to WorkspaceInfo dataclass
- Use slug instead of UUID in join command URL display
- Backward compatible with UUID-based responses
Add Phase 5 section covering 3-pane layout redesign, thread list,
connect agent view, agent profile panel, and file browser placeholders.
Define the foundational network model for the Internet of Agents,
unifying the architecture between the OpenAgents SDK and OpenAgents
Workspace. Covers addressing, verification levels, event model, mods
pipeline, shared resources, discovery, transport bindings, and
cross-network communication.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Section 13 describes how the model relates to AsyncAPI (complementary —
AsyncAPI documents interfaces, the model defines runtime behavior) and
how it differs from Google's A2A protocol (task-centric vs event-centric,
bilateral vs network topology, different discovery and identity models).
Also documents A2A as a transport binding for interoperability.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds three new subsections to Section 1: The Problem (fragmentation across
identity, discovery, communication, boundaries, extensibility), What This
Document Defines, and Design Philosophy (events not requests, networks as
bounded contexts, progressive verification, mods as extensibility,
transport-agnostic).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Phases A-E of workspace migration from openagents-web monorepo into SDK:

- ONM core primitives: Event, Pipeline, Mods, Addressing (65 tests)
- Event-native backend: FastAPI with mod pipeline (auth→workspace→persistence)
  - POST/GET /v1/events, /v1/join, /v1/leave, /v1/heartbeat, /v1/discover
  - Workspace CRUD: /v1/workspaces (43 backend tests)
- Frontend: Next.js workspace UI with event-native API layer
  - Converter functions translate ONM events ↔ component types
  - Single discovery poll replaces separate agent/session polling
- SDK migration: WorkspaceClient, adapters, MCP server, CLI
  - session_id → channel_name throughout
  - All endpoints switched to event-native API
  - X-Workspace-Token auth header
- Deployment: entrypoint.sh with Alembic migrations, production
  docker-compose, nginx reverse proxy
@nebukaga nebukaga merged commit e35c645 into develop Mar 3, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants