Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .changeset/soft-agents-discover.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"@sapiom/harness": minor
---

Discover markerless `defineAgent` and legacy `defineOrchestration` projects plus literal direct source invocations with bounded syntax-only analysis, conservative reconciliation, and live rail/system-graph updates without importing, bundling, type-checking, or executing project code. Public direct invocation edges use `basis: "static-invocation"`. Markerless agents inside nested Git repositories are discovered when that repository is selected directly; package-wide output-to-input data flow remains a separate evidence provider.

**Breaking:** Public system-graph invocation edges that previously carried `basis: "static"` now carry `basis: "static-invocation"`.

**Migration:** Consumers that validate or deserialize `GET /api/workspaces/:workspaceKey/system-graph` responses must accept `"static-invocation"` as the invocation-edge `basis` value before upgrading.
228 changes: 143 additions & 85 deletions packages/harness/docs/agent-discovery.md

Large diffs are not rendered by default.

91 changes: 57 additions & 34 deletions packages/harness/docs/workspace-system-graph.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,14 @@ GET /api/workspaces/:workspaceKey/system-graph
POST /api/workspaces/:workspaceKey/system-graph/refresh
```

`GET` returns the current process-memory snapshot. A cold read waits for the
initial projection, concurrent cold reads share that build, and later reads
reuse it. `POST .../refresh` reruns registry prerequisites, requests a fresh
projection, waits for that attempt, and is the explicit recovery action after
an error. Both successful routes return `200` with a
`GET` returns the current accepted process-memory snapshot. On a cold read,
known inventory nodes and revision-matched navigation render immediately in a
degraded projection; bounded direct invocation extraction and background discovery
may publish a later revision. Concurrent reads share that work, and ordinary
reads never await a filesystem baseline or discovery scan. `POST .../refresh`
reruns registry prerequisites, requests a fresh projection, waits for that
attempt, and is the explicit recovery action after an error. Both successful
routes return `200` with a
`SystemGraphSnapshot`:

```ts
Expand Down Expand Up @@ -105,38 +108,57 @@ within a bounded loop.

`SystemGraph` is path-free and has `kind: "system"`. Its scope repeats only the
opaque key. Nodes contain an `id`, Project-scoped `agentKey`, and display
`label`. Edges are static `invokes` relationships with a `blocking` or `async`
mode. Blocking and asynchronous calls between the same pair remain distinct in
the JSON even when the UI groups them into one connector.
`label`. Public direct-invocation edges are explicit and extensible:

```ts
interface StaticInvocationGraphEdge {
from: string;
to: string;
kind: "invokes";
basis: "static-invocation";
mode: "blocking" | "async";
}

type SystemGraphEdge = StaticInvocationGraphEdge;
```

Blocking and asynchronous calls between the same pair remain distinct in the
JSON even when the UI groups them into one connector.

Direct invocation analysis is syntax-only. It never creates a TypeScript
`Program` or `TypeChecker`, and it never imports, bundles, or executes customer
code. The provider scans each inventoried agent source root separately for
literal calls. It does not inspect the provenance of invocation inputs, follow
agent outputs through formatter/helper/router code, or scan arbitrary workspace
router modules outside those roots. Cross-agent output-to-input analysis will
use a separate package-level evidence provider.

Projection can remain useful while reporting warnings:

| Warning code | Meaning |
| ----------------------------- | ------------------------------------------------------------------------------------------------- |
| `unresolved-target` | A literal target does not resolve to an agent in the selected Project. |
| `dynamic-target` | Source contains a call whose target cannot be proven statically. |
| `duplicate-edge` | The same mode-specific relationship was discovered more than once. |
| `projection-failed` | A relationship projection failed and the remaining graph was preserved. |
| `duplicate-edge` | The same mode-specific direct invocation was discovered more than once. |
| `projection-failed` | A direct invocation projection failed and the remaining graph was preserved. |
| `duplicate-agent-key` | More than one contained agent proposed the same key; local fallback identities disambiguate them. |
| `inventory-extraction-failed` | One agent could not be enriched, so the remaining inventory was returned. |

Registry-known agents enter a working-tree package inventory and render
immediately; source inspection does not block the first graph. An unresolved
agent uses a safe provisional marker or `local:` identity. After the snapshot
and its navigation sidecar commit, source-name inspection runs in the
background. A valid current source definition name becomes canonical and
publishes a newer graph revision, while the older marker remains only a
compatibility alias. An absent or invalid name preserves the provisional node
and any unambiguous direct edges.

Cacheability follows whether identity work has finished, not whether it found
a canonical name. While any source identity is pending, or a failed inspection
could still succeed against unchanged source, the snapshot is `degraded` and
uncached so a provisional identity cannot be frozen in place. Once every
identity has settled, the snapshot is `ready` and cached. Invalid and settled
unavailable identities keep their sanitized per-agent warnings; retryable
failures retain the graph's Retry affordance. A source edit invalidates the
affected identity and projects it again.
Registry and syntax-discovered agents enter a working-tree package inventory
and render immediately. A syntax-proven source definition name is canonical
without bundling or executing project code; a retained marker/cloud slug remains
only a compatibility alias. Unknown or invalid identity uses a safe provisional marker
or `local:` key. Marker-authorized legacy name inspection and direct invocation
extraction run in bounded background queues after the inventory projection
commits. Settled identities and invocation edges publish later revisions;
failures preserve provisional nodes and unambiguous direct edges.

Cacheability keeps three private facts separate: workspace discovery must be
complete, identity work must be settled, and direct invocation extraction must
be complete. A settled unavailable identity may therefore remain provisional
while the snapshot is `ready`; an incomplete workspace walk, pending/retryable
identity, or incomplete invocation scan keeps it `degraded`. Warnings and the
Retry affordance remain visible without freezing evidence that may still change.

Package inventory protocol 1 is deliberately limited to which agents exist,
their stable identities, and their package-relative locations. It carries no
Expand All @@ -161,10 +183,11 @@ The event is an invalidation hint. Clients compare its key and revision with
the displayed snapshot and refetch when newer; the graph itself is not sent on
the event bus.

Opening a Project graph starts one session-independent recursive filesystem
watcher for that Project. This is additional to session and Canvas watchers so
the graph stays current even when no coding-agent session is open. Source and
inventory events are debounced. Platforms without recursive watch support, or
watchers that later error, fall back to asynchronous polling. Removing a
Project retires its watcher and process-memory snapshot once Studio no longer
exposes that scope.
Opening a Project graph acquires a canonical-root watcher lease. Sessions and
graphs for the same root share its bounded asynchronous fingerprint rather than
multiplying recursive walks. Relevant raw events synchronously make old
navigation inert; source/inventory reconciliation is debounced, coalesced, and
generation-guarded. Platforms without recursive watch support, or watchers that
later error, fall back to asynchronous polling over the same admitted candidate
and dependency observations. Removing the final lease retires the watcher and
degrades its accepted freshness proof before a later reopen can reuse it.
Loading
Loading