Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
dc26c9e
feat(agent): prepare trusted runtimes with isolated project tools
kojiwakayama Sep 10, 2026
87c7875
feat(agent): install executor profiles restricted to project tools
kojiwakayama Sep 10, 2026
e576e6a
test(agent): make native fixture field ordering explicit
kojiwakayama Sep 10, 2026
a2c1e08
Merge branch 'feat/trusted-hosted-agent-delivery' into feat/project-t…
kojiwakayama Sep 10, 2026
17db73f
fix(agent): derive project tool run authority from execution kind
kojiwakayama Sep 10, 2026
8c7df56
Merge remote-tracking branch 'origin/feat/trusted-hosted-agent-delive…
kojiwakayama Sep 10, 2026
3ec1daf
fix(agent): bind installed project tools to canonical execution
kojiwakayama Sep 10, 2026
92e432f
Merge remote-tracking branch 'origin/main' into feat/trusted-hosted-a…
kojiwakayama Sep 10, 2026
3c7b550
Merge remote-tracking branch 'origin/feat/trusted-hosted-agent-delive…
kojiwakayama Sep 10, 2026
40743f9
test(agent): use explicit execution kind in native project fixture
kojiwakayama Sep 10, 2026
6f325eb
fix(agent): preserve executor tool authority under patched collections
kojiwakayama Sep 10, 2026
0692303
Merge remote-tracking branch 'origin/feat/trusted-hosted-agent-delive…
kojiwakayama Sep 10, 2026
9ef5d48
fix(agent): capture project installation authority before discovery
kojiwakayama Sep 10, 2026
ebe8c03
test(agent): target the denied tool in the collection attack probe
kojiwakayama Sep 10, 2026
d22abb6
Merge remote-tracking branch 'origin/feat/trusted-hosted-agent-delive…
kojiwakayama Sep 10, 2026
ebefaf0
fix(agent): include project aliases in the metadata allowance
kojiwakayama Sep 10, 2026
a1abd4c
Merge remote-tracking branch 'origin/feat/trusted-hosted-agent-delive…
kojiwakayama Sep 10, 2026
20d29a0
fix(agent): preserve explicitly scoped project tool context
kojiwakayama Sep 10, 2026
12856f4
Merge commit '20d29a0442' into feat/project-tools-executor-install
kojiwakayama Sep 10, 2026
1b7a856
fix(agent): validate executor profile and capture approved tool identity
kojiwakayama Sep 10, 2026
399b923
test(agent): assert scoped skill context in native runtime fixture
kojiwakayama Sep 10, 2026
9cafe78
Merge commit '399b92322f' into feat/project-tools-executor-install
kojiwakayama Sep 10, 2026
0ad9423
Merge remote-tracking branch 'origin/main' into rebase-4477
kwakayama Sep 10, 2026
43c286b
fix(discovery): include colocated tools in runtime catalog
kwakayama Sep 10, 2026
8dddac5
fix(agent): preserve source policy for project tools
kwakayama Sep 10, 2026
c2cdbd3
style(agent): format project runtime regression
kwakayama Sep 10, 2026
141da27
fix(agent): scope inline project tools to selected agent
kwakayama Sep 10, 2026
3a5a981
fix(agent): build isolated project catalogs under source policy
kojiwakayama Sep 10, 2026
d816943
fix(agent): scope full runtime inline tools to project policy
kojiwakayama Sep 10, 2026
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
8 changes: 4 additions & 4 deletions docs/api-reference/veryfront/agent.md
Original file line number Diff line number Diff line change
Expand Up @@ -1932,10 +1932,10 @@ import {

#### Functions

| Name | Description | Source |
| ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
| `initializeExecutorRuntimeContracts` | Install the fixed first-party contracts required before executor project imports. Concurrent and repeated startup preserves any already-registered trusted generation. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/executor-runtime-contracts.ts) |
| `startExecutorRuntimeEntrypoint` | Dedicated executor entrypoint. The reviewed image launcher registers its first-party SchemaValidator, Bundler, ModuleLexer and SkillDocumentParserProvider before calling this function. The fixed image manifest is outside the project tree and is never selected by channel input. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/executor-runtime-entrypoint.ts) |
| Name | Description | Source |
| ------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
| `initializeExecutorRuntimeContracts` | Install the fixed first-party contracts required before executor project imports. Concurrent and repeated startup preserves any already-registered trusted generation. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/executor-runtime-contracts.ts) |
| `startExecutorRuntimeEntrypoint` | Dedicated executor entrypoint. The reviewed image launcher registers its first-party SchemaValidator, Bundler, ModuleLexer and SkillDocumentParserProvider before calling this function. The fixed image manifest is outside the project tree and is never selected by channel input. The default runtime profile installs agent grants and capabilities. The project-tools profile installs only fixed-context project tool operations; it cannot prepare or stream agents. | [source](https://github.com/veryfront/veryfront-code/blob/main/src/agent/hosted/executor-runtime-entrypoint.ts) |

### `veryfront/agent/identity`

Expand Down
68 changes: 63 additions & 5 deletions docs/guides/agent-service-runtime.md
Original file line number Diff line number Diff line change
Expand Up @@ -445,11 +445,69 @@ parser, then supplies the Operator allocation environment and image
manifest. Missing runtime contracts fail startup.

The executor accepts one authenticated `runtime.install` message bound to its
allocation, invocation, generation, owner, and immutable source. Discovery and
runtime preparation remain unavailable until installation succeeds. The
installation carries runtime grants and capability IDs. Initial checkpoint
state uses a separate bounded stream so durable replay state can exceed the
installation message limit.
allocation, invocation, generation, owner, and immutable source. Discovery remains
unavailable until installation succeeds. The trusted image launcher selects the
profile at startup; channel messages cannot change it.

### Installation profiles

| Startup `mode` | Installation data | Operations after installation |
| ------------------- | -------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
| `runtime` (default) | Runtime grant, capability IDs, optional host-tool aliases | `discovery.describe`, `agent.describe`, `runtime.prepare`, `agent.stream` |
| `project-tools` | Fixed agent/project/run context, canonical tool allowlist, call and concurrency limits | `discovery.describe`, `agent.describe`, `project.tool-aliases`, `tool.sources`, `tool.list`, `tool.execute` |

The full-runtime profile uses a separate bounded stream for initial checkpoint
state, so durable replay state can exceed the installation message limit.

The project-tools profile is selected by
`startExecutorRuntimeEntrypoint({ mode: "project-tools" })`. Its installation has
the following shape; the broker supplies the actual allocation and source identities:

```json
{
"version": 1,
"mode": "project-tools",
"binding": {
"allocationId": "allocation-example",
"generation": 1,
"invocationId": "invocation-example"
},
"owner": { "scopeKind": "project", "projectId": "project-example" },
"source": { "type": "release", "releaseId": "release-example" },
"root": "project",
"context": {
"agentId": "assistant",
"projectId": "project-example",
"runId": "run-example"
},
"allowedToolNames": ["inspect"],
"maxCalls": 32,
"maxConcurrent": 2
}
```

`allowedToolNames` contains unique canonical names, with at most 1024 entries.
`maxCalls` is an integer from 1 to 4096; `maxConcurrent` is an integer from 1 to 32.
The fixed context binds tool calls to the admitted canonical run. Correlation IDs,
cancellation and progress use the authenticated channel. This payload accepts no
runtime grants, private credentials or host capability IDs, and rejects unknown
fields. This profile exposes neither runtime preparation nor agent streaming;
the trusted broker owns the agent loop and privileged operations.

The fixed context also accepts optional `userId` and `projectSlug` from the approved
execution grant. Project tools receive those captured values; caller conflicts fail.
An explicitly enabled project source can receive the current call's `activeSkillId`
and bounded `activeSkillToolAvailability`. Omitted skill fields clear prior values.
Credentials and other caller context fields do not cross the project channel.
Unknown startup `mode` values fail before bootstrap configuration or artifact access.
Selected inline tools and discovered tools are combined under the exact project
source policy, including metadata access and later execution. Framework-generated
agent runtime tools are excluded from the project-only catalog, even when their
names appear in a grant.
The full-runtime profile applies the same source-policy scope while extracting
inline tools and reading their metadata during preparation.

### Broker composition

The broker owns HTTP authentication, credentials, model and tool authorization,
and durable persistence. Executor facades call these capabilities through the
Expand Down
158 changes: 158 additions & 0 deletions src/agent/hosted/executor-project-install.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import "#veryfront/schemas/_test-setup.ts";
import { assertEquals, assertRejects } from "#veryfront/testing/assert.ts";
import { describe, it } from "#veryfront/testing/bdd.ts";
import type { JsonValue } from "#veryfront/schemas/index.ts";
import type { ExecutorOperation, ExecutorOperationContext } from "../executor/channel.ts";
import { createExecutorRuntimeInstallation } from "./executor-runtime-install.ts";

const binding = { allocationId: "allocation", invocationId: "invocation", generation: 1 };
const artifact = {
version: 1,
owner: { scopeKind: "global", serviceName: "synthetic-service" },
source: { type: "release", releaseId: "synthetic-release" },
root: "project",
} as const;
const request = {
...artifact,
binding,
mode: "project-tools",
context: { agentId: "coder", projectId: "synthetic-project", runId: "synthetic-run" },
allowedToolNames: ["inspect"],
maxCalls: 32,
maxConcurrent: 2,
} as const;
const context = (): ExecutorOperationContext => ({
binding,
signal: new AbortController().signal,
deadline: Date.now() + 10_000,
});

async function call(
operations: ReadonlyMap<string, ExecutorOperation>,
name: string,
value: unknown,
) {
const operation = operations.get(name);
if (operation?.mode !== "unary") throw new Error("Missing unary operation");
return await operation.handle(value as JsonValue, context());
}
function fixture(pending?: Promise<void>) {
let starts = 0;
let closes = 0;
const retired = Promise.withResolvers<void>();
const operations = new Map<string, ExecutorOperation>([
["discovery.describe", { mode: "unary", handle: () => ({ discovered: true }) }],
["agent.describe", { mode: "unary", handle: () => ({ described: true }) }],
["project.tool-aliases", { mode: "unary", handle: () => ({ aliases: [] }) }],
...["tool.sources", "tool.list", "tool.execute"].map((name): [string, ExecutorOperation] => [
name,
{
mode: "stream",
async *handle() {
await pending;
yield { complete: true };
},
},
]),
]);
const installation = createExecutorRuntimeInstallation({
mode: "project-tools",
binding,
artifact,
install: () => {
starts++;
return Promise.resolve({
operations,
settled: retired.promise,
close: () => {
closes++;
retired.resolve();
return Promise.resolve();
},
});
},
});
return {
installation,
get starts() {
return starts;
},
get closes() {
return closes;
},
};
}

describe("project tool installation", () => {
it("exposes only discovery and project tools after one authenticated installation", async () => {
const f = fixture();
try {
for (
const name of [
"runtime.prepare",
"agent.stream",
"model.generate",
"state.refresh",
"persistence.append",
]
) {
assertEquals(f.installation.operations.has(name), false);
}
await assertRejects(() => call(f.installation.operations, "agent.describe", {}));
assertEquals(f.starts, 0);
assertEquals(await call(f.installation.operations, "runtime.install", request), {
installed: true,
});
assertEquals(await call(f.installation.operations, "agent.describe", {}), {
described: true,
});
await assertRejects(() => call(f.installation.operations, "runtime.install", request));
assertEquals(f.starts, 1);
} finally {
await f.installation.close();
}
assertEquals(f.closes, 1);
});
it("rejects credentials, privileged grants, invalid limits and wrong bindings before discovery", async () => {
const f = fixture();
try {
for (
const invalid of [
{ ...request, credentials: { token: "synthetic-token" } },
{ ...request, capabilities: { persistence: {} } },
{ ...request, grant: { models: [] } },
{ ...request, maxCalls: 4097 },
{ ...request, maxConcurrent: 33 },
{ ...request, allowedToolNames: ["inspect", "inspect"] },
{ ...request, binding: { ...binding, generation: 2 } },
{ ...request, source: { type: "release", releaseId: "other" } },
{ ...request, context: { ...request.context, projectId: null } },
]
) await assertRejects(() => call(f.installation.operations, "runtime.install", invalid));
assertEquals(f.starts, 0);
} finally {
await f.installation.close();
}
});
it("retains an active project tool operation until original work settles during close", async () => {
const work = Promise.withResolvers<void>();
const f = fixture(work.promise);
await call(f.installation.operations, "runtime.install", request);
const execute = f.installation.operations.get("tool.execute");
if (execute?.mode !== "stream") throw new Error("Missing project tool stream");
const iterator = execute.handle({}, context())[Symbol.asyncIterator]();
const next = iterator.next();
let closed = false;
const closing = f.installation.close().then(() => {
closed = true;
});
await Promise.resolve();
await Promise.resolve();
assertEquals(closed, false);
work.resolve();
await next;
await iterator.return?.();
await closing;
assertEquals(f.closes, 1);
});
});
Loading
Loading