186 lines
7.6 KiB
Markdown
186 lines
7.6 KiB
Markdown
# Architecture
|
|
|
|
## Design objective
|
|
|
|
K1412 Agent is a multi-user web coding Agent whose loop can be changed
|
|
independently of its account system and user interface. The architecture keeps
|
|
the high-cost experimental surface small: researchers should be able to change
|
|
context, memory, scheduling, tools, delegation, or completion policy without
|
|
forking authentication, chat history, Docker lifecycle, or the whole frontend.
|
|
|
|
The system deliberately has one Agent path. The earlier idea of a lightweight
|
|
Chat loop beside a custom Work loop was removed because it duplicated product
|
|
behavior, confused model selection, and increased the amount of frontend and
|
|
backend code that had to remain compatible.
|
|
|
|
## System context
|
|
|
|
```mermaid
|
|
flowchart LR
|
|
U["Browser user"] --> P["DNS, TLS and Nginx Proxy Manager"]
|
|
P --> W["Open WebUI<br/>auth, RBAC, history, UI"]
|
|
W --> R["Agent Runtime<br/>loop, context, memory, model routing"]
|
|
R --> M["Model providers<br/>K1412 API or DeepSeek"]
|
|
R --> G["Workspace Gateway<br/>identity and execution policy"]
|
|
G -->|Tailscale + SSH Docker| X["Per-user workspace container<br/>home-node-itx"]
|
|
W --> DB["PostgreSQL<br/>users and chats"]
|
|
R --> DB
|
|
W --> Q["Redis<br/>coordination"]
|
|
```
|
|
|
|
Only Web is publicly reachable. Runtime, Gateway, PostgreSQL, and Redis live on
|
|
private Compose networks. Workspace containers run on a separate physical
|
|
Docker host, receive no application secrets, and expose no host ports.
|
|
|
|
## Responsibility boundaries
|
|
|
|
| Layer | Owns | Must not own |
|
|
| --- | --- | --- |
|
|
| Open WebUI | Registration, login, sessions, admin approval, RBAC, chat persistence, rendering | Agent scheduling, provider secrets, Docker access |
|
|
| Agent Runtime | Model catalog, context policy, Agent loop, tools, scheduling, delegation, memory, plans, run events, evidence gates | Browser sessions, Docker socket |
|
|
| Workspace Gateway | Signed identity enforcement, workspace path policy, per-user locks, Docker lifecycle, file delivery | Model calls, public authentication UI |
|
|
| Workspace container | User code, dependencies, generated files, command execution | Other users' data, service credentials, Docker socket |
|
|
| PostgreSQL | Open WebUI durable state plus Runtime events, plans, and memories | Execution |
|
|
| Shared infrastructure | TLS, registry, source hosting, private routing, model serving | Agent behavior |
|
|
|
|
This separation lets the Web UI be upgraded on its own schedule while the
|
|
Agent loop can iterate frequently.
|
|
|
|
## Request lifecycle
|
|
|
|
### Agent request
|
|
|
|
```mermaid
|
|
sequenceDiagram
|
|
participant B as Browser
|
|
participant W as Open WebUI
|
|
participant R as Runtime
|
|
participant M as Model provider
|
|
participant G as Gateway
|
|
participant X as User workspace
|
|
|
|
B->>W: Authenticated chat request
|
|
W->>R: OpenAI-compatible request + service key + signed user identity
|
|
R->>R: Verify identity, build context, record run.created
|
|
loop Until evidence-backed completion or budget exhausted
|
|
R->>M: Recent context + tools + provider policy
|
|
M-->>R: Assistant content and/or tool calls
|
|
R->>R: Normalize, validate, limit, and schedule calls
|
|
R->>G: Tool request + service key + freshly signed identity
|
|
G->>G: Verify identity and bind user workspace
|
|
G->>X: Execute with resource and path constraints
|
|
X-->>G: Exit code, output, or file result
|
|
G-->>R: Structured tool result
|
|
R->>R: Persist events and evaluate completion evidence
|
|
end
|
|
R-->>W: SSE answer and completed tool detail blocks
|
|
W-->>B: Rendered Agent response
|
|
```
|
|
|
|
The identity sent by Open WebUI is verified once when Runtime accepts the
|
|
request. Runtime then signs the already verified identity again for every
|
|
Gateway call. This prevents a long Agent run from reusing an identity token
|
|
that expired while the model or a tool was working.
|
|
|
|
### Workspace file request
|
|
|
|
```text
|
|
browser with Open WebUI session
|
|
-> /api/v1/k1412/workspace/*
|
|
-> Open WebUI verifies the logged-in user
|
|
-> Web signs a short-lived user identity
|
|
-> Gateway verifies service key + signed identity
|
|
-> Gateway selects only the hashed container and volume for that user
|
|
-> browse, download, or streamed archive response
|
|
```
|
|
|
|
The browser never receives a Gateway service key and never connects to Docker.
|
|
|
|
## Runtime internals
|
|
|
|
Runtime is an OpenAI-compatible provider from Open WebUI's point of view, but
|
|
normal chat completions are implemented by the K1412 loop:
|
|
|
|
- `ModelProvider` translates the internal model spec to a provider request and
|
|
normalizes streamed completion responses.
|
|
- `ContextPolicy` removes rendered tool-detail markup, prepends the system
|
|
contract and durable memories, and keeps the newest visible messages within
|
|
a per-model character budget.
|
|
- `ToolRegistry` declares tool schemas and routes workspace tools to Gateway or
|
|
state tools to `RuntimeStore`.
|
|
- `AgentLoop` owns iteration, tool normalization, validation, scheduling,
|
|
recovery, delegation, and evidence-gated completion.
|
|
- `RuntimeStore` persists run events, per-chat plans, and user memories.
|
|
|
|
More detail is in [agent-loop.md](agent-loop.md).
|
|
|
|
## Workspace architecture
|
|
|
|
`local-docker` and `ssh-docker` implement the same `ExecutionProvider`
|
|
contract. Production uses `ssh-docker`: Gateway talks to Docker on
|
|
`home-node-itx` through a dedicated SSH configuration mounted read-only.
|
|
|
|
Each immutable Open WebUI user ID maps to a SHA-256-derived workspace ID and
|
|
therefore to exactly one:
|
|
|
|
- container named `k1412-ws-<workspace-id>`;
|
|
- named volume `k1412-ws-data-<workspace-id>`;
|
|
- bridge network `k1412-ws-net-<workspace-id>`.
|
|
|
|
Containers run as UID/GID 1000 with a read-only root filesystem, all Linux
|
|
capabilities dropped, `no-new-privileges`, PID/CPU/memory limits, no Docker
|
|
socket, and no published ports. `/workspace` is the only persistent writable
|
|
filesystem. `/tmp` is writable but mounted `noexec`.
|
|
|
|
Python environments live at `/workspace/.venv`. User-level home and package
|
|
caches live below `/workspace/.agent`. The image puts the virtual environment
|
|
and user binary directories first on `PATH`. Commands run under Bash with
|
|
`pipefail`, so a failed install cannot be hidden by a successful `tail` or
|
|
similar pipeline stage.
|
|
|
|
When `WORKSPACE_IMAGE` changes to a new immutable tag, Gateway replaces a stale
|
|
container on its next access but reattaches the existing named volume. Image
|
|
upgrades therefore change the execution environment without deleting user
|
|
files.
|
|
|
|
## Networks and deployment
|
|
|
|
Production runs as one Unraid Compose Manager project:
|
|
|
|
- `public`: Web ingress;
|
|
- `internal`: private service-to-service traffic;
|
|
- `egress`: Runtime provider access and Gateway SSH access.
|
|
|
|
Only Web publishes NAS port `12004`. Nginx Proxy Manager on the VPS terminates
|
|
TLS for `agent.k1412.top` and forwards over Tailscale. Images are built for
|
|
`linux/amd64`, pushed to `docker.k1412.top/wuyang/*` with immutable
|
|
timestamp-and-commit tags, and deployed by updating only the relevant service.
|
|
|
|
The documentation portal is packaged inside the Web image at `/doc/`. It does
|
|
not add a public service, port, database, or proxy rule.
|
|
|
|
## Stable and experimental surfaces
|
|
|
|
Stable platform contracts:
|
|
|
|
- authenticated Web-to-Runtime request;
|
|
- signed Runtime/Web-to-Gateway identity;
|
|
- `ExecutionProvider` workspace semantics;
|
|
- per-user volume isolation;
|
|
- run-event persistence;
|
|
- public model IDs;
|
|
- workspace file API.
|
|
|
|
Expected experiment surfaces:
|
|
|
|
- prompt and context policy;
|
|
- memory selection;
|
|
- tool catalog and intent routing;
|
|
- scheduler and parallelism;
|
|
- delegation;
|
|
- recovery and evidence rules;
|
|
- model budgets and routing.
|
|
|
|
Version experiment-sensitive behavior in `run.created` so results remain
|
|
comparable after the implementation changes.
|