@@ -44,6 +44,8 @@ def __init__(
4444 rpc_address : Optional [str ] = None ,
4545 agent_id : Optional [str ] = None ,
4646 badge_token : Optional [str ] = None ,
47+ signing_kid : Optional [str ] = None ,
48+ keys_preloaded : bool = False ,
4749 ) -> None :
4850 """
4951 Initialize SimpleGuard.
@@ -54,12 +56,17 @@ def __init__(
5456 rpc_address: gRPC server address. If None, auto-starts local server.
5557 agent_id: Explicit agent DID. If None:
5658 - In dev_mode: Auto-generates did:key from keypair
57- - Otherwise: Loaded from agent-card.json
59+ - Otherwise: Loaded from agent-card.json (deprecated)
5860 badge_token: Pre-obtained badge token to use for identity. When set,
5961 make_headers() will use this token instead of signing.
62+ signing_kid: Explicit key ID for signing. When provided with agent_id,
63+ skips agent-card.json entirely.
64+ keys_preloaded: If True, skip file-based key loading (keys already
65+ loaded in gRPC server, e.g. from CapiscIO.connect()).
6066 """
6167 self .dev_mode = dev_mode
6268 self ._explicit_agent_id = agent_id
69+ self ._explicit_signing_kid = signing_kid
6370 self ._badge_token = badge_token
6471
6572 # 1. Safety Check
@@ -69,26 +76,29 @@ def __init__(
6976 "This is insecure! Disable dev_mode in production."
7077 )
7178
72- # 2. Resolve base_dir
79+ # 2. Resolve base_dir (skip walking for agent-card.json when identity params provided)
7380 self .project_root = self ._resolve_project_root (base_dir )
7481 self .keys_dir = self .project_root / "capiscio_keys"
7582 self .trusted_dir = self .keys_dir / "trusted"
76- self .agent_card_path = self .project_root / "agent-card.json"
7783
7884 # 3. Connect to gRPC server
7985 self ._client = CapiscioRPCClient (address = rpc_address )
8086 self ._client .connect ()
8187
82- # 4. Load or generate agent identity
88+ # 4. Resolve agent identity
8389 self .agent_id : str
8490 self .signing_kid : str
85- self ._load_or_generate_card ()
91+ self ._resolve_identity ()
8692
8793 # 5. Load or generate keys via gRPC (may update agent_id with did:key)
88- self ._load_or_generate_keys ()
94+ if not keys_preloaded :
95+ self ._load_or_generate_keys ()
96+ else :
97+ logger .info (f"Keys preloaded in gRPC server, skipping file-based key loading" )
8998
9099 # 6. Load trust store
91- self ._setup_trust_store ()
100+ if not keys_preloaded :
101+ self ._setup_trust_store ()
92102
93103 def sign_outbound (self , payload : Dict [str , Any ], body : Optional [bytes ] = None ) -> str :
94104 """
@@ -201,9 +211,17 @@ def __exit__(self, exc_type, exc_val, exc_tb) -> None:
201211 self .close ()
202212
203213 def _resolve_project_root (self , base_dir : Optional [Union [str , Path ]]) -> Path :
204- """Walk up the directory tree to find agent-card.json or stop at root."""
214+ """Resolve the project root directory.
215+
216+ When agent_id is provided explicitly, uses base_dir (or cwd) directly
217+ without walking up the tree looking for agent-card.json.
218+ """
205219 current = Path (base_dir or os .getcwd ()).resolve ()
206220
221+ # When identity params are provided, don't walk looking for agent-card.json
222+ if self ._explicit_agent_id :
223+ return current
224+
207225 search_path = current
208226 while search_path != search_path .parent :
209227 if (search_path / "agent-card.json" ).exists ():
@@ -212,18 +230,31 @@ def _resolve_project_root(self, base_dir: Optional[Union[str, Path]]) -> Path:
212230
213231 return current
214232
215- def _load_or_generate_card (self ) -> None :
216- """Load agent-card.json or generate a minimal one in dev_mode."""
217- # If explicit agent_id was provided, use it
233+ def _resolve_identity (self ) -> None :
234+ """Resolve agent identity from explicit params, agent-card.json (legacy), or dev defaults.
235+
236+ Priority order:
237+ 1. Explicit agent_id + signing_kid params (preferred — no file needed)
238+ 2. Explicit agent_id only (signing_kid defaults to "key-0")
239+ 3. Legacy agent-card.json file (deprecated)
240+ 4. Dev mode auto-generation
241+ """
242+ # Case 1 & 2: Explicit agent_id provided
218243 if self ._explicit_agent_id :
219244 self .agent_id = self ._explicit_agent_id
220- self .signing_kid = "key-0" # Will be updated when keys are generated/loaded
245+ self .signing_kid = self . _explicit_signing_kid or "key-0"
221246 logger .info (f"Using explicit agent_id: { self .agent_id } " )
222247 return
223-
224- if self .agent_card_path .exists ():
248+
249+ # Case 3: Legacy agent-card.json (deprecated path)
250+ agent_card_path = self .project_root / "agent-card.json"
251+ if agent_card_path .exists ():
252+ logger .warning (
253+ "Loading identity from agent-card.json is deprecated. "
254+ "Pass agent_id and signing_kid to SimpleGuard() directly."
255+ )
225256 try :
226- with open (self . agent_card_path , "r" ) as f :
257+ with open (agent_card_path , "r" ) as f :
227258 data = json .load (f )
228259 self .agent_id = data .get ("agent_id" )
229260 keys = data .get ("public_keys" , [])
@@ -233,15 +264,25 @@ def _load_or_generate_card(self) -> None:
233264
234265 if not self .agent_id or not self .signing_kid :
235266 raise ConfigurationError ("agent-card.json missing 'agent_id' or 'public_keys[0].kid'." )
267+ except ConfigurationError :
268+ raise
236269 except Exception as e :
237270 raise ConfigurationError (f"Failed to load agent-card.json: { e } " )
238- elif self .dev_mode :
271+ return
272+
273+ # Case 4: Dev mode — placeholder until key generation
274+ if self .dev_mode :
239275 logger .info ("Dev Mode: Will generate did:key identity from keypair" )
240- # Placeholder - will be updated with did:key after key generation
241- self .agent_id = "local-dev-agent" # Temporary, replaced in _load_or_generate_keys
276+ self .agent_id = "local-dev-agent"
242277 self .signing_kid = "local-dev-key"
243- else :
244- raise ConfigurationError (f"agent-card.json not found at { self .project_root } " )
278+ return
279+
280+ raise ConfigurationError (
281+ "No agent identity configured. Either:\n "
282+ " - Pass agent_id (and optionally signing_kid) to SimpleGuard()\n "
283+ " - Use dev_mode=True for auto-generated identity\n "
284+ " - Use CapiscIO.connect() which handles identity automatically"
285+ )
245286
246287 def _load_or_generate_keys (self ) -> None :
247288 """Load keys or generate them in dev_mode via gRPC.
@@ -290,39 +331,9 @@ def _load_or_generate_keys(self) -> None:
290331 # Save public key
291332 with open (public_key_path , "w" ) as f :
292333 f .write (key_info ["public_key_pem" ])
293-
294- # Update agent-card.json with JWK
295- self ._update_agent_card_with_pem (key_info ["public_key_pem" ])
296334 else :
297335 raise ConfigurationError (f"private.pem not found at { private_key_path } " )
298336
299- def _update_agent_card_with_pem (self , public_key_pem : str ) -> None :
300- """Helper to write agent-card.json with the generated key."""
301- # For simplicity, just create a minimal card
302- # In production, would convert PEM to JWK
303- card_data = {
304- "agent_id" : self .agent_id ,
305- "public_keys" : [{
306- "kty" : "OKP" ,
307- "crv" : "Ed25519" ,
308- "kid" : self .signing_kid ,
309- "use" : "sig" ,
310- # Note: x would need to be extracted from PEM
311- }],
312- "protocolVersion" : "0.3.0" ,
313- "name" : "Local Dev Agent" ,
314- "description" : "Auto-generated by SimpleGuard" ,
315- "url" : "http://localhost:8000" ,
316- "version" : "0.1.0" ,
317- "provider" : {
318- "organization" : "Local Dev"
319- }
320- }
321-
322- with open (self .agent_card_path , "w" ) as f :
323- json .dump (card_data , f , indent = 2 )
324- logger .info (f"Created agent-card.json at { self .agent_card_path } " )
325-
326337 def _setup_trust_store (self ) -> None :
327338 """Ensure trust store exists and add self-trust in dev_mode."""
328339 if not self .trusted_dir .exists () and self .dev_mode :
0 commit comments