Skip to content

feat: add remote proxy runtime - #154

Draft
hvpareja wants to merge 2 commits into
Human-Agent-Society:mainfrom
hvpareja:feat/remote-proxy-runtime
Draft

feat: add remote proxy runtime#154
hvpareja wants to merge 2 commits into
Human-Agent-Society:mainfrom
hvpareja:feat/remote-proxy-runtime

Conversation

@hvpareja

@hvpareja hvpareja commented Jun 30, 2026

Copy link
Copy Markdown

Motivation

Refs #151.

This draft follows the runtime-as-proxy direction discussed in the issue: remote execution is modeled as a first-class AgentRuntime (remote_proxy) instead of as a parallel top-level state bridge. CORAL remains local-workspace-primary, while adapter implementations handle deployment, invocation, and metrics collection for managed remote runtimes.

The core invariant is: CORAL owns task truth; the remote runtime owns execution state; the bridge imports evidence, never authority.

Changes

  • Adds coral.agent.remote adapter contracts, normalized remote state types, adapter loading, and remote-state persistence under .coral/public/remote_state/.
  • Adds explicit remote control-plane types: RemoteWorkspaceGrant for workspace/capability scope and RemoteEvidence for typed remote artifacts/evidence.
  • Adds a remote_proxy built-in runtime plus a local worker process that deploys/invokes a remote adapter and periodically syncs state.
  • Persists remote handles and operation ids so proxy restarts can reconnect and avoid blind redeploy/reinvoke loops.
  • Generates aggregate remote status from per-agent state files instead of having each worker overwrite a shared index.json.
  • Moves adapter config out of process argv and into the worker environment.
  • Registers remote_proxy/remote in the runtime registry and exposes remote state in CLI/web status.
  • Updates agent binding doctor behavior for runtimes that intentionally do not have a local CLI binary.
  • Documents the local-workspace-primary contract, evidence-not-authority boundary, grant shape, evidence envelope, and adapter config shape.

Test plan

  • uv run --extra dev ruff check . passes.
  • uv run --extra dev ruff format --check . passes.
  • uv run --extra dev python -m pytest tests/ -q passes: 558 passed, 1 skipped.

Affected areas

  • coral/agent/ (runtime, manager, heartbeat, warmstart)
  • coral/grader/ (daemon, TaskGrader, subprocess grader, loader)
  • coral/hub/ (attempts, notes, skills, checkpoint)
  • coral/workspace/ (project setup, worktrees, grader env)
  • coral/cli/ (commands, helpers)
  • coral/hooks/ (post_commit / submit_eval)
  • coral/template/ (CORAL.md, bundled skills/agents)
  • coral/gateway/ (LiteLLM gateway)
  • coral/web/ (dashboard)
  • examples/ (new or modified task)
  • docs/ (docs site)
  • CI / tooling / packaging
  • Other:

Checklist

  • Title follows Conventional Commits (feat:, fix:, docs:, refactor:, ...).
  • uv run pytest tests/ -v passes locally. Equivalent command used: uv run --extra dev python -m pytest tests/ -q.
  • uv run ruff check . and uv run ruff format --check . pass. Equivalent commands used with --extra dev.
  • Added or updated tests under tests/ for any behavior change.
  • Updated docs (docs/content/, README, or relevant skill under .claude/skills/) for any user-visible or contract change.
  • If this changes a config field, CLI flag, hook, or runtime contract — noted the migration path in the PR description.
  • New examples/<task>/: coral validate <task> succeeds and a smoke run produces at least one finalized score. No hidden answer keys committed under seed/.
  • AI-assisted PR: a human author has read every changed line and can defend the design. See AGENTS.md.

@vercel

vercel Bot commented Jun 30, 2026

Copy link
Copy Markdown

@hvpareja is attempting to deploy a commit to the yhy-4770's projects Team on Vercel.

A member of the Team first needs to authorize it.

@hegu-1

hegu-1 commented Jul 14, 2026

Copy link
Copy Markdown

Thanks @hvpareja — I read through #154. The runtime-as-proxy move is the right boundary for this first version.

I would make one invariant explicit in the contract:

CORAL owns task truth. The remote runtime owns execution state. The bridge imports evidence, never authority.

A few concrete places where that invariant could be hardened:

  1. Control-plane grant

RemoteAgentSpec currently carries agent_id, name, code_path, and free-form metadata. I would avoid letting code_path or adapter config implicitly become authority. The remote request should carry an explicit grant/scope: workspace or repo identity, permitted operations, memory/artifact namespace, grant id, issuer, and expiry. The local path is a locator; the grant is the authority.

  1. Typed evidence instead of an opaque artifacts dict

RemoteAgentState.artifacts is currently free-form. A minimal evidence envelope could include:

artifact_id
kind
source_runtime
runtime_id
collected_at
digest
provenance
trust_level

Remote status=completed should remain an observation. CORAL's grader should produce the completion decision and reference the artifact IDs/digests it accepted. That preserves the distinction between evidence and trusted completion.

  1. Recovery and idempotency

The current worker calls deploy() and then invoke() on every local start, while extract_session_id() returns None. After a proxy crash or host restart, that can create a second remote deployment or duplicate invocation.

I would persist the RemoteAgentHandle before invocation and require an operation/idempotency key. Restart should first reconcile:

  • known handle + reachable runtime -> reconnect and continue collection;
  • known handle + missing runtime -> mark degraded/recoverable;
  • invocation outcome unknown -> query by operation id before retrying;
  • remote completion without local grader acceptance -> pending_evidence, not completed.
  1. State ownership under concurrency

Each proxy worker currently writes the shared .coral/public/remote_state/index.json. With multiple agents or adapters, the last writer can replace the index produced by another worker. Per-agent state files are already a good primitive; the aggregate index should either be merged by a single CORAL-owned process or treated as a generated view over those files.

None of these require a remote-primary workspace protocol. They keep #154 additive while making the local control plane genuinely authoritative rather than only described that way.

@hvpareja

Copy link
Copy Markdown
Author

Quick transparency note before anything else: I have not reviewed this update line by line yet. The latest changes were produced with AI assistance, and I still need to do my own human review before marking the PR ready.

Thanks @hegu-1 — this is exactly the kind of boundary I wanted to make explicit.

I pushed an update to #154 that tries to address these points without turning the first version into a remote-primary workspace protocol.

Concretely:

  • Added RemoteWorkspaceGrant so authority is explicit instead of being implied by code_path or adapter config.
  • Added RemoteEvidence as a typed evidence envelope, replacing the previous opaque artifact shape.
  • Documented the invariant: CORAL owns task truth; the remote runtime owns execution state; the bridge imports evidence, never authority.
  • Persisted RemoteAgentHandle and an operation_id before invocation so restarts can reconnect and avoid blind redeploy/reinvoke loops.
  • Treated missing known remote handles as degraded/recoverable state.
  • Changed remote state aggregation so each worker writes per-agent state and the aggregate view is generated from those files, avoiding last-writer-wins on index.json.
  • Moved adapter config out of process argv.

I kept this intentionally minimal: no remote-primary workspace sync, no governance engine, and no full capability enforcement yet. The goal is to make the additive local-workspace-primary contract safer and more explicit.

Would welcome another pass from you on whether the control-plane/evidence boundary now matches what you had in mind.

@hegu-1

hegu-1 commented Jul 15, 2026

Copy link
Copy Markdown

Thanks @hvpareja — and thanks for the transparency note. I reviewed the hardening commit rather than only the PR summary.

The control-plane/evidence boundary now matches the framing I had in mind:

  • RemoteWorkspaceGrant makes authority explicit instead of deriving it from a path or adapter configuration.
  • RemoteEvidence correctly keeps remote completion as evidence rather than CORAL task truth.
  • Per-agent state files remove the shared-index last-writer-wins problem.
  • Persisting the handle before invocation is the right recovery primitive.

There is still one important recovery gap before I would call the invocation path idempotent.

If the process crashes after the remote side has accepted/completed invoke(), but before the local operation record is rewritten from started to completed, restart reads started and calls invoke() again. Passing the same operation_id helps only if every adapter independently enforces idempotency; the adapter contract currently has no query/reconcile operation that CORAL can rely on.

I would make the recovery contract explicit:

  1. persist prepared before dispatch;
  2. transition to dispatched with the remote operation reference;
  3. on restart, query/reconcile by operation_id;
  4. if the remote outcome cannot be established, surface uncertain and do not automatically invoke again;
  5. only a confirmed absent operation may be retried with the same idempotency key.

Two related details:

  • The current operation_id is a deterministic hash of agent_id + prompt + task_name. A legitimate new run with the same inputs can therefore collide with an old completed operation and be skipped. I would separate a CORAL-generated logical run id from the idempotency key: resume reuses the run id; a new run gets a new one.
  • _handle_reachable() currently returns True when list_agents() raises. That turns an unavailable control plane into permission to continue. I would return unknown/degraded and defer invocation instead.

One smaller follow-up for the generated aggregate: per-agent files now need expiry/tombstone semantics. Otherwise an agent that disappears from collect_metrics() remains in the aggregate indefinitely, while the aggregate collected_at is refreshed at read time and can make stale evidence look current.

So: the authority/evidence ownership is now correct. The remaining issue is narrower and mechanical — make started/dispatched/uncertain recovery a real adapter-level contract rather than assuming the repeated call is safe.

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