Effort: thorough
Most of the 58+ tools wrapped in scidex/forge/tools.py and
scidex/forge/forge_tools.py accept untyped **kwargs, log the call
via @log_tool_call (line 67), and return whatever the upstream API
gave back. Bad args fail late or silently — typically the upstream
returns a 400 with a string body that the tool re-raises wrapped in
"Tool failed". Operators chase symptoms instead of the real cause
(wrong arg name, wrong type, missing API key). This spec wraps every
tool in a pydantic-validated input model + a standard
ToolResult{ok, data, error{code,detail,upstream}} envelope, with a
backwards-compat shim so callers that expect raw dicts keep working.
scidex/forge/tool_envelope.py:class ToolError(BaseModel):
code: Literal['validation','upstream','rate_limit',
'auth','unknown']
detail: str
upstream: dict | None = None
class ToolResult(BaseModel):
ok: bool
data: Any | None
error: ToolError | None@typed_tool(input_model=Cls) wraps a toolhttpx.HTTPStatusError is caught and bucketed429→rate_limit, 401|403→auth, 4xx→validation,5xx→upstream); successful return is wrapped inToolResult(ok=True, data=...).
legacy=True flag on thedata directlytool_envelope_adoption(skill_id, last_legacy_call_at)tool_invocations.times_used:log_tool_callscidex/forge/forge_tools.py:67) gains an error_codetool_invocations (migration). The senate"Tool 'pubmed_search' got pmid=str expected list[str]; full
validation: ..." — no stack trace dump in the user-visibletests/test_typed_tools.py:ToolResult(ok=False,
error.code='validation') and never reaches upstreamrequests mock asserts no calls).ok=False, error.code='rate_limit'.ok=True, data=..., and the decoratortool_invocations row with error_code IS NULL.pubmed_search) end-to-end as the proof oflegacy=True flag.
tool_invocations.error_code.scidex/forge/forge_tools.py — log_tool_call call-site.q-devx-skill-scaffolder — new tools auto-use the envelope.q-obs-trace-id-propagation — envelope is the natural place toDelivered:
scidex/forge/tool_envelope.py (new) — ToolError, ToolResult pydantic models;@typed_tool(input_model, legacy=True) decorator; current_error_code() ContextVarlog_tool_call can log the bucketed error code; _bucket_http_error()requests.HTTPError and httpx.HTTPStatusError via duck-typing); tool_envelope_adoptiontool_calls.error_code column bootstrapped at import time.scidex/forge/tools.py (modified) — Imports typed_tool, current_error_code,log_tool_call to log error_code (from ContextVar) totool_calls.error_code. Applied @typed_tool + pydantic input model to 5 tools:PubmedSearchInput, SemanticScholarSearchInput, GtexTissueExpressionInput,ChemblDrugTargetsInput, AlphafoldStructureInput.tool_calls.error_code TEXT added; tool_envelope_adoption(skill_id,tests/test_typed_tools.py (new) — 28 tests: validation failure never hits{
"completion_shas": [
"1dc92d178"
],
"completion_shas_checked_at": ""
}