Skip to content
Open
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
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ agentic-controller/
internal/controller/ Controller implementations
internal/registry/ OCI registry client
docs/adr/ Architecture Decision Records
harness/ Migration harness (Go) baked into the base image
images/ Agent container images (base, runtime, language)
clients/ Reference client stack (client core, hub-shim,
browser UI, agent harnesses, demo) — see clients/README.md
skills/ Agent skills for contributors
CONTEXT.md Domain glossary
AGENTS.md Agent-facing instructions
Expand Down
8 changes: 8 additions & 0 deletions changes/unreleased/clients-reference-stack.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
kind: feature

description: >
Import the reference client stack under clients/ — isomorphic client
core, hub-shim (the proposed Hub passthrough proxy surface), PatternFly
browser UI, mock and goose agent harnesses, in-cluster deploy manifests,
and the end-to-end demo — plus ADRs 0004 (verified client contract and
transports) and 0005 (platform-resolved params).
5 changes: 5 additions & 0 deletions clients/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
node_modules/
dist/

.claude/settings.local.json
.DS_Store
69 changes: 69 additions & 0 deletions clients/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# clients/ — reference client stack

Working client-side implementation of this repository's AgentRun/ACP
contract, developed against the live controller on minikube and verified
end-to-end with both a deterministic mock agent and a real goose+Bedrock
agent. It exists so the Hub, UI, and base-image streams can build against
running code instead of a whiteboard:

- the **hub-shim** HTTP surface is the proposed contract for the Hub
passthrough proxy (stream 2, issue #21) — see ADR 0004
- the **UI** and **client core** are the reference for the tackle2-ui
client layer (stream 3, issues #22–#24)
- the **goose harness** is a working reference for the interactive
(ACP-serving) side of the base image (stream 4, issues #25/#26). The
batch-mode harness landed separately as the repo-root `harness/` Go
module + `images/agent-base-goose-java` (#33); both consume the same
`KONVEYOR_MODEL_*` env contract.

## Layout

| Path | What |
|------|------|
| `packages/agentic-client/` | Isomorphic (browser + node) client core: CRD contract types + helpers, `AcpSession` over WebSocket (JSON-RPC 2.0), hub-shim HTTP transport. Zero runtime dependencies. |
| `packages/agentrun-client/` | Node-side client: create/watch AgentRuns against the apiserver, resolve the ACP endpoint (`status.sandboxName` → pod, `status.secretKeyRef` → key), port-forward tunnel. Shaped for konveyor/editor-extensions. |
| `packages/hub-shim/` | Localhost gateway serving the SHIM HTTP API v1 (REST + WS `/acp` proxy with server-side `X-Secret-Key` injection). Stands in for the future Hub passthrough proxy. |
| `ui/` | Browser SPA (PatternFly): run list, create form with platform-resolved params (ADR 0005), streaming chat with permission (HITL) round-trips. |
| `harness-mock/` | Deterministic ACP agent (no LLM): real protocol via the ACP SDK's server side, scripted behaviors (`TEST_PERMISSION`, `TEST_CANCEL`, `TEST_DROP`). Test fixture and demo agent. |
| `harness-goose/` | Real agent base: goose `serve` on `:4000` behind an entrypoint that adapts the controller's `KONVEYOR_*` env contract (clones the `repository` param, maps model env, writes prompt hints). |
| `deploy/` | In-cluster deployment of gateway + UI (interim, pre-Hub). |
| `manifests/` | Sample Agent/LLMProvider CRs: `samples.yaml` (mock) and `goose-bedrock.yaml` (real goose on AWS Bedrock). |
| `hack/` | `demo-up.sh` / `demo-check.sh` / `demo-down.sh` — converge the cluster, smoke the full ACP round-trip, tear down. |
| `docs/DEMO.md` | Narrated end-to-end demo runbook (browser + IDE + real agent). |

Architecture write-ups live with the repo's ADRs: `docs/adr/0004`
(verified client contract and layered transports) and `docs/adr/0005`
(platform-resolved params).

## Quickstart

Prereqs: minikube running, Agent Sandbox v0.5.0 installed, the controller
deployed from the repo root (`make docker-build deploy IMG=agentic-controller:dev`),
Node ≥ 22.9.

```sh
cd clients
hack/demo-up.sh # converge cluster + start shim & UI, prints URLs
hack/demo-check.sh # smoke: full ACP round-trip on a throwaway mock run
hack/demo-down.sh # stop local processes; cluster untouched
```

Then follow `docs/DEMO.md`.

## Status: interim by design

The controller and CRDs are the real ones from this repository. The rest
is scaffolding with a named replacement:

- `hub-shim` → the real Hub passthrough proxy (its route table is the
handover spec)
- `ui` → absorbed into tackle2-ui
- `harness-goose` → the stream-4 base image (the batch side already exists:
repo-root `harness/` + `images/agent-base-goose-java`, #33; this one
covers the interactive `goose serve` / ACP mode until they converge)
- `harness-mock` → stays, as a deterministic test fixture

Every piece encodes contract facts verified against the live controller
(pod name == `status.sandboxName`, ACP Secret data key `secret-key`,
headless portless Service, whole-spec immutability) — see ADR 0004 for
the full list and rationale.
74 changes: 74 additions & 0 deletions clients/deploy/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# In-cluster deployment (interim, pre-Hub)

Runs the browser UI + gateway **on the cluster** — no laptop processes.
Both components are interim by design: the gateway seat is replaced by the
real Konveyor Hub passthrough proxy later, and the UI page is absorbed
into tackle2-ui (see `docs/adr/0004-client-contract-and-transports.md`).

```
browser ── ingress/route (TLS + SSO) ── agentic-ui (nginx, static SPA)
│ /api + WS, same-origin
agentic-gateway (SA + RBAC)
│ CRs via k8s API · pod :4000 via service DNS
agentic-controller / Agent Sandbox / sandbox pods
```

Differences from laptop mode: the gateway **direct-dials**
`<sandboxName>.<ns>.svc:4000` (no port-forwarding; auto-detected
in-cluster, `ACP_DIAL` overrides) and authenticates as a namespace-scoped
ServiceAccount instead of your kubeconfig.

## Build the images (context = repo root)

```sh
# local cluster (minikube):
minikube image build -t agentic-gateway:dev -f deploy/gateway/Dockerfile .
minikube image build -t agentic-ui:dev -f deploy/ui/Dockerfile .

# real cluster: docker build + push, then kustomize-patch the image refs.
```

## Deploy

```sh
kubectl apply -k deploy/manifests
kubectl -n konveyor-agents rollout status deploy/agentic-gateway deploy/agentic-ui
```

Prereqs already on the cluster: the agentic-controller (deployed from
the repo root: `make deploy`), Agent Sandbox, and the sample
Agents/LLMProviders (`hack/demo-up.sh` converges all of that).

## Access

With an ingress (see `ingress.example.yaml`) — or without one:

```sh
kubectl -n konveyor-agents port-forward svc/agentic-ui 8081:80
open http://localhost:8081
```

Everything (REST + WebSocket chat) flows through that single origin.

## Verify

The browser-constraint smoke works through the deployed stack end to end
(nginx → gateway → controller → sandbox ACP):

```sh
cd packages/hub-shim
SHIM_URL=http://localhost:8081 npx tsx dev/browser-smoke.ts
```

## Teardown

```sh
kubectl delete -k deploy/manifests
```

## Auth (deliberate gap)

The gateway has no auth of its own — protect it at the ingress (oauth2-proxy,
or an OpenShift Route with an oauth-proxy sidecar). Per-user identity and
RBAC on runs is precisely the value the real Hub proxy adds; bolting a full
auth stack onto this interim component would be building Hub twice.
28 changes: 28 additions & 0 deletions clients/deploy/gateway/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# agentic-gateway: the hub-shim packaged for in-cluster use.
# Build context is the REPO ROOT (the shim imports ../agentrun-client/src):
# docker build -t agentic-gateway:dev -f deploy/gateway/Dockerfile .
# minikube image build -t agentic-gateway:dev -f deploy/gateway/Dockerfile .
FROM node:22-slim

WORKDIR /app

# Install each package's deps (tsx runs the TypeScript sources directly,
# matching the repo's dev style; dev deps are required for that).
COPY packages/agentrun-client/package*.json packages/agentrun-client/
COPY packages/hub-shim/package*.json packages/hub-shim/
RUN cd packages/agentrun-client && npm ci --no-audit --no-fund \
&& cd ../hub-shim && npm ci --no-audit --no-fund

COPY packages/agentrun-client packages/agentrun-client
COPY packages/hub-shim packages/hub-shim

# In-cluster defaults: bind all interfaces; ACP dial mode auto-detects
# "direct" from the serviceaccount env (no port-forwarding in-cluster).
ENV HOST=0.0.0.0 \
PORT=7080 \
NAMESPACE=konveyor-agents

USER node
EXPOSE 7080
WORKDIR /app/packages/hub-shim
CMD ["npx", "tsx", "src/server.ts"]
99 changes: 99 additions & 0 deletions clients/deploy/manifests/gateway.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# agentic-gateway: in-cluster stand-in for the future Hub passthrough proxy.
# Namespace-scoped RBAC — exactly the surface the SHIM API v1 needs.
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: agentic-gateway
namespace: konveyor-agents
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: agentic-gateway
namespace: konveyor-agents
rules:
- apiGroups: ["konveyor.io"]
resources: ["agentruns"]
verbs: ["get", "list", "watch", "create", "delete"]
- apiGroups: ["konveyor.io"]
resources: ["agents", "llmproviders"]
verbs: ["get", "list"]
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get"]
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: agentic-gateway
namespace: konveyor-agents
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: agentic-gateway
subjects:
- kind: ServiceAccount
name: agentic-gateway
namespace: konveyor-agents
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: agentic-gateway
namespace: konveyor-agents
labels:
app.kubernetes.io/name: agentic-gateway
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: agentic-gateway
template:
metadata:
labels:
app.kubernetes.io/name: agentic-gateway
spec:
serviceAccountName: agentic-gateway
containers:
- name: gateway
image: agentic-gateway:dev
imagePullPolicy: IfNotPresent
ports:
- containerPort: 7080
name: http
env:
- name: NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
readinessProbe:
httpGet:
path: /healthz
port: http
initialDelaySeconds: 2
periodSeconds: 5
resources:
requests:
cpu: 50m
memory: 128Mi
limits:
memory: 512Mi
Comment on lines +59 to +84

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Missing securityContext hardening across both deploy Deployments. Neither agentic-gateway nor agentic-ui sets a pod/container securityContext, so both run with the default (root-capable, writable-rootfs, privilege-escalation-allowed) context. Same fix pattern applies to both; the gateway is higher priority since it also holds RBAC to read secrets and pods.

  • clients/deploy/manifests/gateway.yaml#L59-L84: add securityContext.runAsNonRoot: true + seccompProfile.type: RuntimeDefault at pod level, and allowPrivilegeEscalation: false, readOnlyRootFilesystem: true, capabilities.drop: ["ALL"] on the gateway container.
  • clients/deploy/manifests/ui.yaml#L21-L40: add the same pod-level securityContext, and allowPrivilegeEscalation: false + capabilities.drop: ["ALL"] on the ui container (if adding readOnlyRootFilesystem: true, mount emptyDir volumes for nginx's writable dirs, e.g. /var/cache/nginx, /tmp, /var/run).
📍 Affects 2 files
  • clients/deploy/manifests/gateway.yaml#L59-L84 (this comment)
  • clients/deploy/manifests/ui.yaml#L21-L40
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@clients/deploy/manifests/gateway.yaml` around lines 59 - 84, Harden both
Deployments: in clients/deploy/manifests/gateway.yaml lines 59-84, add pod-level
runAsNonRoot and RuntimeDefault seccompProfile, plus container-level
allowPrivilegeEscalation false, readOnlyRootFilesystem true, and drop ALL
capabilities for gateway; in clients/deploy/manifests/ui.yaml lines 21-40, add
the same pod-level settings and container-level privilege-escalation and
capability restrictions for ui, adding emptyDir mounts for nginx writable
directories if enabling readOnlyRootFilesystem.

Source: Linters/SAST tools

---
apiVersion: v1
kind: Service
metadata:
name: agentic-gateway
namespace: konveyor-agents
labels:
app.kubernetes.io/name: agentic-gateway
spec:
selector:
app.kubernetes.io/name: agentic-gateway
ports:
- name: http
port: 7080
targetPort: http
29 changes: 29 additions & 0 deletions clients/deploy/manifests/ingress.example.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# EXAMPLE — not applied by the kustomization. Adapt host/class/TLS to your
# environment and put SSO in front (oauth2-proxy annotations, or on
# OpenShift use a Route with an oauth-proxy sidecar instead).
#
# Only the UI needs exposing: nginx inside agentic-ui proxies /api (HTTP +
# WebSocket) to the gateway Service, so one host serves everything
# same-origin.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: agentic-ui
namespace: konveyor-agents
annotations:
# WebSocket sessions are long-lived:
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
spec:
ingressClassName: nginx
rules:
- host: agentic.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: agentic-ui
port:
number: 80
16 changes: 16 additions & 0 deletions clients/deploy/manifests/kustomization.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

# In-cluster deployment of the interim agentic UI + gateway.
# Prereqs: the agentic-controller and Agent Sandbox are already installed
# (see manifests/controller/install.yaml), and the agentic-gateway:dev /
# agentic-ui:dev images are loadable by the cluster (minikube image build,
# or retag + push to a registry and patch the images below).
resources:
- gateway.yaml
- ui.yaml

# Ingress is environment-specific — see ingress.example.yaml. Put TLS and
# SSO (oauth2-proxy / OpenShift Route auth) in front: the gateway itself
# is deliberately auth-less, exactly like the future Hub proxy seat it
# stands in for is not.
Loading
Loading