Skip to content

feat: waiting state with approval workflows, VC-based authorization, and multi-version reasoners#197

Merged
AbirAbbas merged 58 commits intomainfrom
feat/waiting-state
Mar 2, 2026
Merged

feat: waiting state with approval workflows, VC-based authorization, and multi-version reasoners#197
AbirAbbas merged 58 commits intomainfrom
feat/waiting-state

Conversation

@AbirAbbas
Copy link
Contributor

@AbirAbbas AbirAbbas commented Mar 2, 2026

Summary

Adds the waiting state execution model and human-in-the-loop approval workflows to the control plane, along with VC-based authorization, multi-version reasoner support, and connector sidecar management APIs.

Waiting State & Approval Workflows

  • New execution state waiting — agents can pause mid-execution to request external approval
  • POST /executions/:id/request-approval transitions execution to waiting and stores approval metadata (request ID, callback URL, expiry)
  • GET /executions/:id/approval-status polls approval state
  • POST /webhooks/approval receives approval decisions via webhook (supports both flat JSON and hax-sdk envelope formats)
  • HMAC-SHA256 webhook signature verification for secure callbacks
  • Approval decisions: approved, rejected, request_changes, expired
  • Idempotent webhook processing (duplicate deliveries return already_processed)
  • DB migrations 024-026 for approval state columns

VC-Based Authorization & Access Policies

  • W3C Verifiable Credential authorization for agent-to-agent interactions
  • Tag-based access policies with approval workflows (AccessRulesTab, AgentTagsTab UI)
  • DID authentication middleware and DID-web service
  • Permission middleware with capability-scoped checks
  • Tag normalization and VC verification services
  • DB migrations 018-023 for permissions, DIDs, access policies, and agent tag VCs

Multi-Version Reasoners

  • Agents can register multiple versions of the same reasoner
  • Traffic weight routing between reasoner versions
  • Version management via connector sidecar APIs

Connector Sidecar Management APIs

  • Full connector handler suite: policy management, tag management, DID management, reasoner management, status read, observability config
  • Connector auth middleware (X-Connector-Token) with capability gating

SDK Updates (all three: Go, Python, TypeScript)

  • ApprovalClient / approval API methods for requesting and polling approvals
  • DIDAuthenticator for DID-based request signing
  • LocalVerifier for offline VC verification
  • Execution status enums updated with waiting state
  • Agent registration supports version metadata

Web UI

  • Authorization page with Access Rules and Agent Tags tabs
  • Execution approval panel in execution detail view
  • Removed dead filter components (ExecutionFilters, SearchWithFilters, etc.)
  • Vitest testing infrastructure with StatusBadge sample tests
  • Chip input component, tooltip tag list, and other UI additions

Examples

  • Waiting state examples in all three SDKs (Python, TypeScript, Go)
  • Multi-version reasoner examples in all three SDKs
  • Permission agent examples (agent A + agent B) in all three SDKs

Infrastructure

  • PostgreSQL testing support in dev.sh
  • Gin route parameter conflict fix between waiting-state and tag-vc endpoints
  • Release workflow fix to prevent infinite CI loops ([skip ci] on version bumps)
  • CODEOWNERS file

Testing

  • All CI checks passing (33/33 functional tests, Go/Python/TypeScript SDK tests, CodeQL)
  • Functional test suite includes 7 new waiting state tests covering: full approval flow, rejection, expiration, webhook idempotency, duplicate request handling, and hax-sdk envelope format
  • Unit tests for approval handlers, webhook handlers, DID auth middleware, permission middleware, access policy service, tag approval service, VC verification
  • VC authorization integration test (1295 lines)

Migrations

9 new migrations (018–026):

  • 018_create_permission_approvals — approval tracking table
  • 019_create_did_documents — DID document storage
  • 020_create_protected_agents — agent protection flags
  • 021_create_access_policies — tag-based access rules
  • 022_create_agent_tag_vcs — VC-tag associations
  • 023_drop_dead_tables — cleanup
  • 024_execution_approval_state — waiting state columns on executions
  • 025_approval_callback_url — callback URL column
  • 026_approval_expires_at — expiry tracking column

Stats

241 files changed, 39,791 insertions(+), 12,014 deletions(-)

AbirAbbas and others added 30 commits February 10, 2026 14:29
This commit introduces the foundation for the new VC-based authorization
system that replaces API key distribution with admin-approved permissions.

Key components added:
- Architecture documentation (docs/VC_AUTHORIZATION_ARCHITECTURE.md)
- Database migrations for permission approvals, DID documents, and protected agents
- Core types for permissions and did:web support
- DIDWebService for did:web generation, storage, and resolution
- PermissionService for permission requests, approvals, and VC issuance

The system enables:
- Agents self-assigning tags (identity declaration)
- Admin approval workflow for protected agent access
- Real-time revocation via did:web
- Control plane as source of truth for approvals

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…entation

- Add DID authentication middleware with Ed25519 signature verification
- Add permission checking middleware for protected agent enforcement
- Implement admin API handlers for permission management (approve/reject/revoke)
- Add permission request and check API endpoints
- Implement storage layer for DID documents, permission approvals, protected agent rules
- Add comprehensive integration test suite (14 test functions covering all phases)
- Add Admin UI pages: PendingPermissions, PermissionHistory, ProtectedAgents
- Add Go SDK DID authentication support
- Add Python SDK DID authentication support
- Fix CI to enable FTS5 tests (previously all SQLite-dependent tests were skipped)
- Add security documentation for DID authentication
- Add implementation guide documentation

Co-Authored-By: Claude <noreply@anthropic.com>
TestGetNodeDetailsHandler_Structure expected HTTP 400 for missing route
param but Gin returns 404. TestGetNodeStatusHandler_Structure was missing
a mock expectation for GetAgentStatus causing a panic.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The CI workflow change from `go test ./...` to `go test -tags sqlite_fts5 ./...`
caused previously-skipped tests to execute, revealing 15 pre-existing bugs:

- UI handler tests: Register agents in storage and configure mocks for
  GetAgentStatus calls; fix assertions to match actual behavior (health
  check failures mark agents inactive, not error the request)
- VC service tests: Fix GetWorkflowVC lookups to use workflow_vc_id not
  workflow_id; fix issuer mismatch test to tamper VCDocument JSON instead
  of metadata field; fix error message assertion for empty VC documents
- VC storage tests: Fix GetWorkflowVC key lookups; fix empty result assertions
- PresenceManager tests: Register agents in storage so markInactive ->
  UpdateAgentStatus -> GetAgentStatusSnapshot -> GetAgent succeeds; add
  proper sync.Mutex for callback vars; use require.Eventually instead of
  time.Sleep; set HardEvictTTL for lease deletion test
- Webhook storage: Fix hardcoded Pending status to use webhook.Status
- Execution records test: Fix LatestStarted assertion (CreateExecutionRecord
  overwrites updated_at with time.Now())
- Cleanup test: Wire countWorkflowRuns and deleteWorkflowRuns into
  workflow cleanup path

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ntext cancellation

Multiple SSE tests called req.Context().Done() expecting it to cancel the
context, but Done() only returns a channel — it doesn't cancel anything.
This caused SSE handler goroutines to block forever, leaking and eventually
causing a 10-minute test timeout in CI.

Fixed all affected tests to use context.WithCancel + explicit cancel() call,
matching the pattern already used by the working SSE tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…n config

Add two example agents for manually testing the VC authorization system
end-to-end: permission-agent-a (caller) and permission-agent-b (protected
target). Enable authorization in the default config with seeded protection
rules.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ster_agent_with_did

The previous commit added identity_package access and client credential
wiring to _register_agent_with_did but didn't update the test fakes.
_FakeDIDManager now provides a realistic identity_package and
_FakeAgentFieldClient supports set_did_credentials, so the full
registration path is exercised in tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add Go permission test agents (caller + protected target with 3 reasoners)
- Add TS permission test agents (caller + tag-protected target with VC generation)
- Fix TS SDK DID auth: pass pre-serialized JSON string to axios to ensure
  signed bytes match what's sent on the wire
- Fix Python SDK test for async execution manager payload serialization change
- Add go-perm-target protection rule to config
- Gitignore compiled Go agent binaries

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The execute() method now passes a JSON string instead of an object to
axios for DID auth signing consistency. Update test assertion to match.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ror propagation

- Fix re-approval deadlock: expand auto-request condition to trigger for
  revoked/rejected statuses, not just empty (permission.go)
- Fix empty caller_agent_id: add DID registry fallback in
  ResolveAgentIDByDID for did:key resolution (did_service.go, did_web_service.go)
- Fix HTTP 200 for failed executions: return 502 with proper error details
  when inner agent-to-agent calls fail (execute.go)
- Fix error propagation across all 3 SDKs:
  - Go SDK: add ExecuteError type preserving status code and error_details
  - TS SDK: propagate err.responseData as error_details in all error handlers
  - Python SDK: add ExecuteError class, extract JSON body from 4xx responses
    instead of losing it via raise_for_status(), propagate error_details in
    async callback payloads

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Tests expected the old 3-header format ({timestamp}:{bodyHash}) but the
implementation correctly uses 4 headers with nonce ({timestamp}:{nonce}:{bodyHash}),
matching Go and Python SDKs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Addresses code scanning alert about missing rate limiting on the
authorization route handler. Adds a sliding-window rate limiter
(30 requests per IP per 60s) to the local verification middleware.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace custom Map-based rate limiter with express-rate-limit package,
which CodeQL recognizes as a proper rate limiting implementation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
AbirAbbas and others added 13 commits February 20, 2026 17:13
Resolve conflicts in async_execution_manager.py and client.py by
keeping both auth_headers (from main) and did_authenticator (from
this branch) parameters.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
# Conflicts:
#	control-plane/agentfield-server
Resolve conflicts in Python SDK: keep both DID auth (feat/connector)
and typed exceptions (main) imports. Use AgentFieldClientError for
network errors while preserving detailed error body parsing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… for fresh DBs

Two CI failures:

1. linux-tests: stubStorage in server_routes_test.go was missing the
   DeleteAgentVersion method added to the StorageProvider interface
   by the multi-version work. Add the stub.

2. Functional Tests (postgres): migrateAgentNodesCompositePKPostgres
   tried to ALTER TABLE agent_nodes before GORM created it on fresh
   databases. The information_schema.columns query returns count=0
   (not an error) when the table doesn't exist, so the function
   proceeded to run ALTER statements against a nonexistent table.
   Add an explicit table existence check matching the pattern already
   used by the SQLite migration path.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@AbirAbbas AbirAbbas requested a review from a team as a code owner March 2, 2026 15:42
Resolve 4 conflicts from main's v0.1.43-rc.1 release merge:
- config.go: keep approval workflow env var overrides (new feature code)
- execute.go: keep renderStatusWithApproval() helper (actively called)
- server.go: adopt main's :agentId param naming for /agents/ route
- __init__.py: keep approval type exports (used by client.py/agent.py)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@github-actions
Copy link
Contributor

github-actions bot commented Mar 2, 2026

Performance

SDK Memory Δ Latency Δ Tests Status
Python 9.3 KB +4% 0.34 µs -3%
Go 231 B -18% 0.67 µs -33%
TS 487 B +39% 1.28 µs -36%

Regression detected:

  • TypeScript memory: 350 B → 487 B (+39%)

AbirAbbas and others added 8 commits March 2, 2026 11:19
The merge brought in a test from main that expected 42 columns in the
workflow execution insert query, but the feature branch added
approval_expires_at as the 43rd column. Update the test's column list
and expected placeholder count to match.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Ruff lint flagged the unused import (F401). The tests use httpx_mock
fixture from pytest-httpx, not httpx directly.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Python SDK:
- Add pytest-httpx dependency (with Python >=3.10 constraint)
- Register httpx_mock marker for --strict-markers compatibility
- Add importorskip for graceful skip on Python <3.10
- Fix request_approval test calls to match actual API signature

TypeScript SDK:
- Call server.closeAllConnections() before server.close() in
  afterEach to prevent keep-alive connection timeout in tests

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
After the reasoner name fix, @agent.reasoner(name="reports_generate")
registers at /reasoners/reports_generate (the explicit name), not
/reasoners/generate_report (the function name).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ag-vc endpoints

The waiting-state feature added routes under /api/v1/agents/:node_id/...
which conflicted with the existing tag-vc endpoint using :agentId as
the parameter name. Gin requires consistent wildcard names for the same
path segment, causing a panic on server startup.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ional tests

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@AbirAbbas AbirAbbas changed the title Feat/waiting state feat: waiting state with approval workflows, VC-based authorization, and multi-version reasoners Mar 2, 2026
@AbirAbbas AbirAbbas merged commit 414f91c into main Mar 2, 2026
39 checks passed
@AbirAbbas AbirAbbas deleted the feat/waiting-state branch March 2, 2026 19:17
AbirAbbas added a commit that referenced this pull request Mar 2, 2026
Main added an internalToken parameter to ExecuteHandler in PR #197.
Update the two test call sites to pass empty string for the new param.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
github-merge-queue bot pushed a commit that referenced this pull request Mar 2, 2026
* fix: allow empty input for parameterless skills/reasoners (#196)

Remove binding:"required" constraint on Input field in ExecuteRequest and
ExecuteReasonerRequest structs. Gin interprets required on maps as
"must be present AND non-empty", which rejects the valid {"input":{}}
payload that SDKs send for parameterless calls.

Also remove the explicit len(req.Input)==0 check in prepareExecution and
add nil-input guards in the reasoner and skill handlers to match the
existing pattern in execute.go.

Closes #196

* test: strengthen empty-input handler coverage

* fix: update empty_input_test.go for ExecuteHandler signature change

Main added an internalToken parameter to ExecuteHandler in PR #197.
Update the two test call sites to pass empty string for the new param.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Abir Abbas <abirabbas1998@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
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.

2 participants