Skip to content

Commit dff3cd0

Browse files
authored
Add Qoder Forward template support (#35)
* Add Qoder Forward template support Change-Id: I0e941a1e2ad3c761b2359a19c5c4162e09fc98c7 * Rerun CI when PR descriptions change Change-Id: I2126a26be4cbc875389c1570726cad4f599ef502
1 parent d320f13 commit dff3cd0

52 files changed

Lines changed: 1320 additions & 63 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ name: CI
22

33
on:
44
pull_request:
5+
types: [opened, synchronize, reopened, edited]
56
push:
67
branches: [main]
78

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ packages/playground/web/
2222
# local environment and state
2323
.env
2424
.env.*
25+
/agents.yaml
2526
*.state.json
2627
agents.state.json
2728
agents.synced.yaml

CONTEXT.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,17 @@ The forward-looking risk OpenAgentPack is a bet against: as agents move from "ca
2121
**Capability contract**:
2222
The explicit, per-provider capability matrix OpenAgentPack publishes (native / emulated / unsupported per resource kind). Portability means the *core declaration* is portable plus this contract is explicit and unsupported facets degrade gracefully (e.g. a provider emulating a non-native MCP transport or memory backend) — not that every feature is identical on every provider. Capability tiers are point-in-time: a resource emulated on a provider today may turn native as that provider catches up; only the matrix cell changes, not the resource's declared status.
2323

24+
**Agent Delivery**:
25+
The per-provider choice of how an Agent Harness is exposed for use. `managed` delivery runs sessions directly from a provider-managed Agent; `forward` delivery exposes a reusable Template that requires a business Identity when starting a Session. Delivery belongs to the Agent declaration because it changes that Agent's provider-side form; it is not a Provider connection default.
26+
27+
**Identity**:
28+
A provider-visible business principal on whose behalf a Forward Session runs. An Identity is independent of Templates: one Identity may use several Templates, and one Template may serve several Identities. Session creation consumes an existing Identity selected by the caller; it does not create or own Identities.
29+
2430
## Resources and workflow
2531

2632
OpenAgentPack treats agents as **infrastructure as code**. A single `agents.yaml` declares the desired state and is the single source of truth. A Terraform-style workflow reconciles the real provider to match it: `validate → plan → apply → destroy`.
2733

28-
Declared resources: `environment`, `vault`, `memory_store`, `skill`, `file`, `agent`, `deployment`. `mcp_server` and `multiagent` are expressed through an agent; `session` is a runtime conversation started from a managed agent, not a declared resource. `deployment` declares scheduled/triggered runs of an agent; while every provider is expected to converge on native support, some currently expose it only via emulation — `plan` surfaces the tier and any behavioral differences.
34+
Declared resources: `environment`, `vault`, `memory_store`, `skill`, `file`, `agent`, `deployment`. `mcp_server` and `multiagent` are expressed through an agent; `session` is a runtime conversation started from an applied Agent declaration, not a declared resource. Managed delivery starts it from a provider-managed Agent; Forward delivery starts it from a Template plus a caller-selected Identity. `deployment` declares scheduled/triggered runs of an agent; while every provider is expected to converge on native support, some currently expose it only via emulation — `plan` surfaces the tier and any behavioral differences.
2935

3036
At any moment there are three descriptions: **config** (the YAML, desired state), **state** (a local state file mapping declared resources to remote IDs with content hashes), and **remote** (what actually exists on the provider). `plan` computes the diff; `apply` makes remote match config and updates state; content-hash diffing makes runs incremental; failed dependencies skip their dependents rather than leaving half-built state.
3137

apps/server/openapi.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -719,6 +719,7 @@
719719
"memory_store",
720720
"skill",
721721
"agent",
722+
"template",
722723
"deployment",
723724
"file"
724725
]
@@ -749,6 +750,7 @@
749750
"memory_store",
750751
"skill",
751752
"agent",
753+
"template",
752754
"deployment",
753755
"file"
754756
]
@@ -783,6 +785,7 @@
783785
"memory_store",
784786
"skill",
785787
"agent",
788+
"template",
786789
"deployment",
787790
"file"
788791
]
@@ -838,6 +841,7 @@
838841
"memory_store",
839842
"skill",
840843
"agent",
844+
"template",
841845
"deployment",
842846
"file"
843847
]

apps/webui/src/lib/api/generated/schema.d.ts

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -275,14 +275,30 @@ export interface paths {
275275
message: string;
276276
resource?: {
277277
/** @enum {string} */
278-
type: "environment" | "vault" | "memory_store" | "skill" | "agent" | "deployment" | "file";
278+
type:
279+
| "environment"
280+
| "vault"
281+
| "memory_store"
282+
| "skill"
283+
| "agent"
284+
| "template"
285+
| "deployment"
286+
| "file";
279287
name: string;
280288
provider: string;
281289
};
282290
}[];
283291
missing: {
284292
/** @enum {string} */
285-
type: "environment" | "vault" | "memory_store" | "skill" | "agent" | "deployment" | "file";
293+
type:
294+
| "environment"
295+
| "vault"
296+
| "memory_store"
297+
| "skill"
298+
| "agent"
299+
| "template"
300+
| "deployment"
301+
| "file";
286302
name: string;
287303
provider: string;
288304
}[];
@@ -291,7 +307,15 @@ export interface paths {
291307
action: "create" | "update" | "delete" | "no-op";
292308
address: {
293309
/** @enum {string} */
294-
type: "environment" | "vault" | "memory_store" | "skill" | "agent" | "deployment" | "file";
310+
type:
311+
| "environment"
312+
| "vault"
313+
| "memory_store"
314+
| "skill"
315+
| "agent"
316+
| "template"
317+
| "deployment"
318+
| "file";
295319
name: string;
296320
provider: string;
297321
};
@@ -309,7 +333,15 @@ export interface paths {
309333
};
310334
dependencies: {
311335
/** @enum {string} */
312-
type: "environment" | "vault" | "memory_store" | "skill" | "agent" | "deployment" | "file";
336+
type:
337+
| "environment"
338+
| "vault"
339+
| "memory_store"
340+
| "skill"
341+
| "agent"
342+
| "template"
343+
| "deployment"
344+
| "file";
313345
name: string;
314346
provider: string;
315347
}[];

docs/guides/deploy-to-qoder.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,14 @@ providers:
99
qoder:
1010
api_key: ${QODER_PAT}
1111
gateway: "https://api.qoder.com/api/v1/cloud" # optional; this is the default
12+
forward_gateway: "https://api.qoder.com/api/v1/forward" # optional; derived from gateway by default
1213
```
1314
1415
| Field | Required | Default | Description |
1516
|-------|:--------:|---------|-------------|
1617
| `api_key` | yes | — | Qoder personal access token. Resolve from `.env` with `${QODER_PAT}`. |
1718
| `gateway` | no | `https://api.qoder.com/api/v1/cloud` | Qoder cloud gateway base URL. |
19+
| `forward_gateway` | no | derived from `gateway` | Qoder Forward gateway used for Template lifecycle requests. |
1820

1921
## Capabilities
2022

@@ -74,6 +76,53 @@ agents:
7476
## What Qoder uniquely supports
7577

7678
- **Memory stores** — persistent context for an agent. See [`examples/qoder/with-memory/`](../../examples/qoder/with-memory/).
79+
- **Forward Templates** — declaratively materialize an `agents.*` declaration as a reusable Forward baseline:
80+
81+
```yaml
82+
agents:
83+
forward-assistant:
84+
model: auto
85+
instructions: You are a helpful assistant.
86+
environment: byoc
87+
tunnel: private-network
88+
vault: mcp-credentials
89+
delivery:
90+
qoder:
91+
type: forward
92+
```
93+
94+
`agents plan/apply/destroy` then manage a Qoder Forward Template (`tmpl_...`) instead of a Managed Agent
95+
(`agent_...`). The default remains Managed Agent delivery when `delivery` is omitted.
96+
97+
Identity is optional for local Forward Session testing. If omitted, OpenCMA finds the enabled Qoder Identity whose
98+
`external_id` is `__qca_admin_identity__`, then sends its real `idn_...` id to the Session API. To run as a specific
99+
user—or when the reserved Identity is unavailable—configure an existing Identity explicitly:
100+
101+
```yaml
102+
defaults:
103+
provider: qoder
104+
session:
105+
qoder:
106+
identity_id: idn_xxx
107+
```
108+
109+
Then use the same session command to test a Forward-delivered agent:
110+
111+
```bash
112+
agents session run "Hello" --agent forward-assistant
113+
```
114+
115+
The CLI resolves the applied Template and optional configured Identity, creates a Forward Session, and routes create, send,
116+
stream, get, and archive operations through the Forward gateway. It never creates or updates an Identity as a side effect
117+
of starting a Session. Use `--identity-id idn_xxx` to override the YAML default for one invocation. Managed sessions keep
118+
using the Cloud gateway.
119+
120+
Production messaging delivery is a separate Forward Channel concern. An IM Channel explicitly binds a business Identity
121+
and Template; do not treat the CLI's local testing Identity as an end-user Identity. Qoder
122+
recommends QR-code authorization for Channel binding so channel credentials do not need to be stored locally.
123+
124+
Forward-delivered agents still cannot be referenced by OpenAgentPack deployments; scheduled Managed Deployment runs
125+
require an Agent resource.
77126

78127
## Next steps
79128

docs/guides/run-sessions.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,14 @@ A **session** is a runtime conversation started from a managed agent. Sessions a
1212
agents session run "Summarize the repo structure" --agent assistant
1313
```
1414

15-
`session run` creates a session, sends the prompt, and streams the response. When only one agent is configured, `--agent` is auto-detected.
15+
`session run` creates a session, sends the prompt, and streams the response. When only one agent is configured, `--agent` is auto-detected. For a Qoder agent with `delivery.qoder.type: forward`, Identity is optional: without one OpenCMA looks up the enabled Identity whose `external_id` is `__qca_admin_identity__` and sends its real `idn_...` id. Configure `defaults.session.qoder.identity_id` or pass `--identity-id` to select an existing business Identity. OpenCMA never creates or updates Identity resources implicitly.
1616

1717
Options:
1818

1919
| Option | Description |
2020
|--------|-------------|
2121
| `--agent <name>` | Agent to run (auto-detected with one agent). |
22+
| `--identity-id <id>` | Override the configured Qoder Forward Identity for this Session. |
2223
| `--environment <name>` | Override the agent's declared environment. |
2324
| `--vault <name>` | Override the agent's declared vault. |
2425
| `--memory-stores <names>` | Override the agent's declared memory stores (comma-separated). |
@@ -45,7 +46,7 @@ agents session delete <session-id>
4546

4647
## What a session binds
4748

48-
A session binds an agent + an environment + vaults + memory stores + files into one runnable unit. The bindings are resolved from the agent declaration and the state file; `session create` lets you override `--environment`, `--vault`, and `--memory-stores` at run time.
49+
A Managed Session binds an Agent + environment + vaults + memory stores + files. A Qoder Forward Session binds a Template + Identity; the Template already owns its environment, tunnel, vault, and MCP configuration. `session create` lets callers override the relevant runtime bindings.
4950

5051
## Programmatic usage
5152

docs/reference/cli.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ Manage runtime agent sessions.
111111
| `session events <session-id>` | List event history for a session. |
112112
| `session delete <session-id>` | Delete a session. |
113113

114-
`session create` / `session run` accept `--agent`, `--environment`, `--vault`, `--memory-stores`, `--title`, and `--provider`. `session run` and `session send` accept `--json` (JSONL output) and `--no-stream` (polling instead of SSE). `session list` accepts `--agent` and `--all`; `session events` accepts `--limit`, `--all`, `--json`.
114+
`session create` / `session run` accept `--agent`, `--identity-id`, `--environment`, `--vault`, `--memory-stores`, `--title`, and `--provider`. `--identity-id` selects an existing Qoder Forward Identity and overrides `defaults.session.qoder.identity_id`; when neither is provided, OpenCMA resolves the Identity whose `external_id` is `__qca_admin_identity__`. OpenCMA never creates or updates an Identity. `session run` and `session send` accept `--json` (JSONL output) and `--no-stream` (polling instead of SSE). `session list` accepts `--agent` and `--all`; `session events` accepts `--limit`, `--all`, `--json`.
115115

116116
## `agents deployment`
117117

docs/reference/configuration.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@ The `agents.yaml` schema. Every field below is sourced from the Zod schema in `p
77
```yaml
88
version: "1"
99
providers: { <name>: <provider-config> }
10-
defaults: { provider: <name> | "all" }
10+
defaults:
11+
provider: <name> | "all"
12+
session:
13+
qoder: { identity_id: <existing-idn-id> }
1114
environments: { <name>: EnvironmentDecl }
1215
tunnels: { <name>: TunnelDecl }
1316
vaults: { <name>: VaultDecl }
@@ -23,6 +26,7 @@ deployments: { <name>: DeploymentDecl }
2326
| `version` | string | yes | Schema version. Currently `"1"`. |
2427
| `providers` | map | yes | One block per provider; each holds its credentials. |
2528
| `defaults.provider` | string | no | Default target for `plan`/`apply`. `all` targets every declared provider. |
29+
| `defaults.session.qoder.identity_id` | string | no | Existing Qoder Forward Identity used as the local Session default. If omitted, OpenCMA looks up the enabled Identity whose `external_id` is `__qca_admin_identity__` and sends its real `idn_...` id. Never created or managed by `apply`. |
2630
| `environments` | map | no | Cloud runtimes. |
2731
| `tunnels` | map | no | Existing Qoder BYOC tunnels referenced by sessions; OpenCMA does not manage their lifecycle. |
2832
| `vaults` | map | no | Credential stores. |

packages/cli/src/commands/session.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ export function formatDuration(startIso: string, endIso?: string): string {
4545
interface SessionCreateOpts {
4646
file: string;
4747
agent?: string;
48+
identityId?: string;
4849
environment?: string;
4950
environmentId?: string;
5051
tunnel?: string;
@@ -68,6 +69,7 @@ export async function sessionCreateCommand(
6869
const ctx = await buildCliRuntime(options.file);
6970
const run = await createSessionForAgent(ctx, {
7071
agent: positionalAgent ?? options.agent,
72+
identityId: options.identityId,
7173
provider: options.provider,
7274
environment: options.environment,
7375
environmentId: options.environmentId,
@@ -255,6 +257,7 @@ function renderCollectedEvents(result: CollectedSessionEvents, json: boolean): v
255257
interface SessionRunOpts {
256258
file: string;
257259
agent?: string;
260+
identityId?: string;
258261
environment?: string;
259262
environmentId?: string;
260263
tunnel?: string;
@@ -282,6 +285,7 @@ export async function sessionRunCommand(
282285

283286
const runOptions = {
284287
agent: positionalAgent ?? options.agent,
288+
identityId: options.identityId,
285289
provider: options.provider,
286290
environment: options.environment,
287291
environmentId: options.environmentId,

0 commit comments

Comments
 (0)