feat(identity): Phase 1+2 - connect() API, identity bridge, cache TTL, presence#288
feat(identity): Phase 1+2 - connect() API, identity bridge, cache TTL, presence#288
Conversation
- 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
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
| 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 | ||
|
|
There was a problem hiding this comment.
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.
| @@ -1,3 +1,5 @@ | |||
| """OpenAgents - AI Agent Networks for Open Collaboration.""" | |||
|
|
|||
| __version__ = "0.8.5.post5" | |||
There was a problem hiding this comment.
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.
| __version__ = "0.8.5.post5" | |
| __version__ = "0.8.5.post6" |
| endpoint = endpoint.rstrip("/") | ||
|
|
There was a problem hiding this comment.
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.
| endpoint = endpoint.rstrip("/") |
| try: | ||
| from openagents.connect import connect as identity_connect | ||
| identity_result = await identity_connect( | ||
| name=agent_id, |
There was a problem hiding this comment.
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.
| 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}") |
There was a problem hiding this comment.
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.
| 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}") |
| path.parent.mkdir(parents=True, exist_ok=True) | ||
| path.write_text(json.dumps(data, indent=2, default=str)) | ||
| try: | ||
| path.chmod(0o600) |
There was a problem hiding this comment.
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.
| "cert_serial": cert_serial, | ||
| "origin": origin, | ||
| "created_at": created_at, | ||
| "account_api_key_prefix": api_key[:10] + "...", |
There was a problem hiding this comment.
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.
| "account_api_key_prefix": api_key[:10] + "...", |
| f"cached credentials exist. Use a different name or ensure " | ||
| f"you have the correct API key." |
There was a problem hiding this comment.
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.
| 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." |
| ) | ||
| identity_api_key: Optional[str] = Field( | ||
| default=None, | ||
| description="Account API key (oa-xxxxx) for auto-registering agents in the global registry", |
There was a problem hiding this comment.
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.
| 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." | |
| ), |
- 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
- 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.
c214fa1 to
2ba8a3c
Compare
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
Summary
Phase 1
connect()one-liner API for agent identity registrationconnect_sync()synchronous wrapperAgentIdentitydataclass with credential caching at~/.openagents/agents/register_agent()— auto-registers agents in global registryNetworkConfigidentity fields (enabled, endpoint, api_key, auto_register, origin)Phase 2
cache_ttlparameter inconnect()skips verify-key API call when cache is freshidentity_cache_ttlfield (default 3600s)get_connected_agents()→get_agent_registry()in discovery_connectordocs/guides/identity_quickstart.mdwith SDK, API, and network auto pathsTest plan
connect()successfully registers agent via live API_is_cache_fresh()correctly handles fresh/stale/missing cacheNetworkConfig.identity_cache_ttldefaults to 3600🤖 Generated with Claude Code