Agents (project workspaces)¶
In-product Agents area: personal projects with an on-disk workspace ({OPENKMS_PROJECTS_ROOT}/{project_id}/), Deep Agents chat, file tree, local git, optional skills, and a global skills registry.
| Area | Status |
|---|---|
Sidebar + /agents (Projects) + /agents/skills (Skills) + /projects/{id}/sessions/{sessionId} |
✅ |
Project settings page /projects/{id}/settings (General includes git origin and delete project; Agent + Skills + Schedules + Sharing) |
✅ |
| Session API key (per conversation, creator identity) | ✅ |
| Global skills registry + project install | ✅ |
| Project CRUD + files API | ✅ |
| Conversations + NDJSON stream | ✅ |
| Plan mode toggle | ✅ |
| HITL resume endpoint | ✅ |
| Subagents (explore = workspace files; research = web + skills) | ✅ |
| Local git | ✅ |
| Remote git (HTTPS + PAT) | ✅ |
| Console Kubernetes cluster browse + apply/logs + register Service in Apps | ✅ |
| Project deploy secrets (encrypted → sync Opaque Secret; Agent sees names/keys only) | ✅ |
Deploy via openkms skill (kubernetes apply / register-app / dev-sync) |
✅ (API key needs console:kubernetes; register-app also ontology:write; dev-sync also projects:write) |
Console operators can register clusters under /console/kubernetes (console:kubernetes); see Kubernetes clusters. Project settings → Deploy stores encrypted secrets and syncs them to the cluster; agents only reference Secret names. Workspace shell still runs in the backend/worker process. Operators can apply YAML in the cluster detail page; Agents use the openkms skill (kubernetes apply, kubernetes register-app, kubernetes dev-sync) — kubeconfig is never injected into the workspace. Dev-sync packs project files server-side into a Pod for hot iteration (see Dev sync).
Layout¶
- Agents area: Projects | Skills tabs at
/agentsand/agents/skills. Project settings → General has a danger-zone Delete project (workspace files, sessions, and schedules). - Desktop (≥769px): three columns — left conversation sessions (like KB Q&A; a small running indicator when
last_turn.status=running); center chat thread + collapsible Plan checklist (fromwrite_todos, dismissible) + plan toggle + composer; right file tree + preview split with git actions. Drag pane dividers to resize; widths persist inlocalStorage. Opening Markdown (.md/.markdown) or HTML (.html/.htm) defaults to a rendered Preview (Markdown uses the sameRichMarkdown+ code/Mermaidprecomponent and.document-detail-markdown-bodytypography as document detail;[[target]]/[[target|label]]uses the wiki preprocess and opens another file in this project workspace, relative to the current file — bare names without an extension resolve to.md; HTML in a sandboxed iframe); toggle Source for the line-numbered raw view. Other text files stay Source-only. Assistant replies interleave text with compact tool and subagent rows (click a row to expand input/output); completed turns fold consecutive tool/subagent rows (≥2) into a single summary; the live turn stays expanded. History reloads tool rows from persisted transcripts. Message history is paged (tail first; scroll up loads older). Composer ignores Enter while an IME composition is active. Revert (under a user bubble): removes that message and all later turns, clears LangGraph checkpoint state for the session, and puts the user text back in the composer to edit and resend — refused while a non-stale turn isrunning. Upload menu: pick multiple files or a folder. Refresh, parent-folder navigation, and per-row delete (.openkmsand.openkms/skillsfolders protected; other paths including files under.openkms/may be deleted). - Shell: the app header is hidden (
openkms-agents-fullpage), so the global left rail carries the brand → Home (top) and the account menu (bottom) — see Immersive views. - Phone (≤768px): chat is the primary full-height surface with a compact top bar (back, session title, Sessions / Files / Settings). Sessions and Files each open as a full-width panel (same idea as Documents channel list on phone). Opening a file in Files shows the viewer full-height (tree hidden until closed). Session Review uses Lessons | Improve | Artifacts tabs instead of three columns. App header stays hidden (
openkms-agents-fullpage) — navigation is via the in-workspace chrome.
Session API key¶
Each project conversation (session) gets a dedicated personal API key (purpose=agent_session) owned by the session creator (user_sub). The plaintext token is stored encrypted in agent_conversations.context and injected into the agent shell as:
OPENKMS_API_KEYOPENKMS_API_BASE_URL
Installed skills (e.g. openkms) use this key when run via shell execute. Keys are revoked when the session is deleted. They do not appear in Settings → API keys.
Run skill CLIs from the project root: python .openkms/skills/<skill_id>/scripts/cli.py … — no cd, no pip install (dependencies install with the skill).
Human-in-the-loop (HITL)¶
HITL is optional per tool name in hitl.py (currently none). The interrupt bar supports batched Approve all (N) when enabled. Resume streams into the current assistant turn.
When the agent pauses for approval, the turn is persisted as context.last_turn.status=interrupted with the interrupt payload in context.last_turn.interrupt (merged action_requests when multiple). Leaving the page or sleeping does not lose Approve/Reject: reopening the session restores the bar from last_turn. Resume / completion clears the stored interrupt.
Context compaction (long sessions)¶
Deep Agents compacts older turns when the model context budget is exceeded. Evicted history is offloaded to conversation_history/ in the project workspace; the agent keeps working from a structured internal summary. Compaction summaries are not shown in chat (they are filtered from the stream and stripped before persistence). In a single long turn with many tool calls, compaction may run more than once as context grows — that is normal and stays internal.
Before each workspace turn (interactive chat and scheduled runs), the backend may run the same pre-turn compaction step when the LangGraph checkpoint is over budget (deep_agents/turn_prepare.py). In-turn compaction inside Deep Agents still applies during long tool loops.
Checkpoint-first context (agent memory vs chat UI)¶
| Layer | Role |
|---|---|
agent_messages (Postgres) |
What users see in the thread — user/assistant text and persisted tool trace pills for the UI |
LangGraph checkpoint (thread_id = conversation id) |
What the agent uses on the next turn — full tool messages, subagent state, compaction events |
On revert, checkpoint rows are deleted with the truncated messages so the next turn re-seeds from DB. On normal follow-up turns, only the latest user message is appended to the checkpoint thread (tool outputs are not re-injected from agent_messages, avoiding duplicate tokens). HITL resume continues from the interrupted checkpoint via Command(resume=…) without re-sending DB history.
LangGraph checkpoints use Postgres tables checkpoints, checkpoint_blobs, checkpoint_writes with thread_id = conversation id. The backend checkpointer uses a connection pool so concurrent turns (and revert-then-resend) do not share one psycopg connection. Revert deletes checkpoint rows on the same SQLAlchemy transaction as message deletes (DELETE …/messages/from/{id}).
Image input (multimodal turns)¶
Attach images in the composer — paste, drag & drop, or the paperclip button (PNG / JPEG / WebP / GIF, max 8 MB). Chips preview above the input; send with or without text.
- Storage: object storage under
agent-attachments/{conversation_id}/— deliberately not the project workspace, sogit status, remotes, and the agent's file tools never see chat images. Uploads go toPOST …/conversations/{id}/attachments. - Persistence:
agent_messages.attachmentsholds descriptors only (id,name,mime,size); the image bytes stay in object storage and are inlined as the turn is built. - Turn input:
deep_agents/turn_input.pybuildsHumanMessagecontent blocks (text+image_urlwith a base64 data URI) for both the checkpoint-first append and the DB seed path, so replayed and reverted threads keep their images. Images then live in the checkpoint and are re-sent each turn until compaction offloads them (_aoffload_inline_media). - Cleanup: removing a staged chip deletes its object; posting a message sweeps older unreferenced uploads; deleting a conversation or reverting/truncating messages removes the matching objects. Deleting a conversation purges its whole prefix.
- Model support: needs an image-capable model (deepseek-flash,
visioncapability in Models). Deep Agents replaces content blocks the active model profile rejects, so a text-only model degrades instead of failing. - Discard:
DELETE …/conversations/{id}/attachments/{attachment_id}?mime=…removes a staged image (refused with409once a message references it).
Skills¶
| Layer | Location |
|---|---|
| Registry (global) | {OPENKMS_AGENT_SKILLS_ROOT}/{skill_id}/{version}/ + DB tables agent_skills, agent_skill_versions |
| Installed (per project) | {project_id}/.openkms/skills/{skill_id}/ + projects.settings.installed_skills |
- Upload zip or folder on Agents → Skills (
POST /api/agent-skills). Each version stores a content hash (per-file SHA-256, sorted, aggregated). - Skill settings (
/agents/skills/{skill_id}/settings): general metadata and version upload/delete (same layout as channel settings). - Set default version per skill (used when installing without picking a version).
- Toggle install on new projects in skill settings (
is_default); matching skills auto-install when creating a project. - Project settings → Skills: install/update/uninstall skills from the registry.
Scheduled agent runs¶
- Project settings → Schedules: create cron jobs that send a fixed prompt to the project agent.
- Stateless: new chat session each run; optional delete session after completion.
- Stateful: reuses one existing session (
conversation_id); keeps context between runs. - Not supported: plan mode.
- Unattended: HITL is disabled; tool approvals are auto-approved. Context is compacted before each run when over the model budget (same pre-turn step as interactive chat; see Context compaction above).
- Auth uses a session API key minted for the schedule owner (
owner_sub+ optional realm roles inconfig). - All rows appear in Job runs → Schedules (
/job-runs/schedules); worker taskrun_scheduled_project_agent.
Configuration¶
| Variable | Default | Notes |
|---|---|---|
OPENKMS_PROJECTS_ROOT |
data/projects (local) / /data/projects (Docker) |
One folder per project UUID |
OPENKMS_AGENT_SKILLS_ROOT |
data/agent-skills |
Global skills registry |
OPENKMS_DEEP_AGENT_MODEL_ID |
— | Falls back to OPENKMS_AGENT_MODEL_ID |
OPENKMS_AGENT_LLM_EXTRA_BODY |
unset | Deep Agents façade (deep_agents/llm_chat.py); enable_thinking always forced false |
OPENKMS_AGENT_LLM_REASONING_CONTENT_SHIM |
unset (auto) | Same façade: inject empty reasoning_content on assistant rows for non-api.openai.com bases |
OPENKMS_AGENT_SANDBOX_TIMEOUT_SECONDS |
60 |
Python sandbox in project dir |
OPENKMS_AGENT_LOG_LEVEL |
INFO |
Project agent turn logs (agent_turn_start / agent_turn_done at INFO; agent_turn_failed at ERROR). Use DEBUG for verbose deep-agents detail. |
OPENKMS_BACKEND_LOG_LEVEL |
— | Root log level for all app.* loggers when set (default INFO). |
Observability (without Langfuse)¶
Interactive agent turns run in the backend container; scheduled cron runs run in the worker container. Tail both:
Filter failed turns (visible at default OPENKMS_AGENT_LOG_LEVEL=INFO):
Each turn logs grep-friendly fields: turn_id, project_id, conversation_id, duration_ms, and on failure error=…. Scheduled run failures also appear in Job runs → detail (worker_log).
The latest turn summary is stored on the conversation as context.last_turn (turn_id, status, error, duration_ms, tool_count, optional assistant_message_id, optional interrupt when status=interrupted) — visible via GET …/conversations/{id} for debugging. Interactive stream turns commit the user message and an empty assistant row before the agent loop (status=running), then throttle-update the assistant row (about every 2s and on tool events) while the model runs. Persisted tool_calls include wiki_tool_traces_v1 (model replay) and wiki_assistant_stream_parts_v1 (interleaved text + tool order for the chat UI after reload). Older assistant rows that only have traces (no stream parts) cannot recover mid-turn order; the SPA falls back to text then folded tools. The agent loop itself runs in a background task detached from the browser connection: leaving the page or the machine sleeping cancels only the NDJSON stream, not the turn. Reopening the session polls while last_turn.status is running (and not stale — same ~2h window as the API / STALE_RUNNING_SECONDS) and reloads messages until the turn leaves running. Switching to another session aborts the browser’s NDJSON reader (frees the composer on the other session) while the server turn continues; returning to the session attaches via poll / mid-turn DB rows. Failed stream turns persist an assistant message in the thread and emit an NDJSON error line (detail + persisted message) when the client is still connected. GET …/messages?tail=true returns the last page in one request. Deleting a session clears the LangGraph checkpoint thread and is refused while a non-stale turn is running.
Langfuse (optional): Same variables as qa-agent: LANGFUSE_SECRET_KEY, LANGFUSE_PUBLIC_KEY, and LANGFUSE_BASE_URL must all be set or tracing is off. Pass optional session_id on POST …/messages to group turns in one Langfuse Session (defaults to conversation id). Tags: deep-agent, project-stream | project-sync, and plan-mode when applicable. LANGFUSE_HEALTHCHECK (default true) probes the host before callbacks; LANGFUSE_TRACE_STREAMING (default true) controls streaming turns.
Project search: In Agent settings, enable web search and pick a search_tool connector. Stored as web_search and search_connector_id in projects.settings.
Docker: projects_data volume on backend and worker (include agent-skills under the same volume or a sibling mount).
Troubleshooting¶
| Symptom | Likely cause | What to check |
|---|---|---|
| Toast network error / fetch failed after ~5 min pending | Proxy read timeout while the agent is still running but not streaming bytes (long tool/LLM gap). Stack: browser → host nginx → Docker frontend nginx (proxy_read_timeout 300s in docker/nginx-frontend.conf) → backend. Turn usually keeps running server-side; reopen the session to see progress / final reply. |
Raise proxy_read_timeout (and proxy_send_timeout) on both nginx layers for location /api/; tail host /var/log/nginx/error.log for upstream timed out; check context.last_turn. |
| Empty chat after leave/sleep (pre-fix) | Stream was bound to the HTTP request: disconnect cancelled the turn and rolled back the uncommitted messages. | Fixed: early commit + background turn + poll on reopen. Redeploy backend/frontend if still seen on an old build. |
| Mid-turn UI frozen after fast session switch | Older SPA builds could drop the live NDJSON bridge without re-attaching poll. | Current SPA aborts NDJSON on leave, marks running optimistically, and polls when the session is visible again. Hard-refresh if on an old bundle. |
409 A turn is already running |
Another interactive turn for the session has last_turn.status=running (or a stale one under ~2h). |
Wait / reopen session; if the API process died mid-turn, wait for stale timeout or inspect last_turn. |
agent_turn_failed in backend logs with LLM/tool text; error line in chat after refresh |
In-app failure (misconfigured model, recursion limit, tool error). HTTP 200 + NDJSON error. |
docker compose logs -f backend 2>&1 \| rg 'ERROR.*agent_turn'; conversation context.last_turn. |
400 reasoning_content … must be passed back (DeepSeek thinking) |
Thinking mode on + tools; history missing reasoning_content. Deep Agents disables thinking + shims via deep_agents/llm_chat.py. |
Redeploy backend; or set OPENKMS_AGENT_LLM_REASONING_CONTENT_SHIM=true. |
another command is already in progress (psycopg) on revert + resend |
Fixed: checkpointer now uses AsyncConnectionPool. Redeploy backend if still seen on an old image. |
Ensure no overlapping streams on the same session (wait for revert toast before resend). |
| Empty chat after reload, toast on load | GET …/messages failed (auth/network). |
Browser network tab; backend access log. |
Local dev (./dev.sh, Vite 5173) has no nginx; long-turn timeouts are uncommon unless a host reverse proxy sits in front.
Runtime (Deep Agents)¶
Workspace chat uses deepagents ≥0.7.21. The Plan checklist UI depends on write_todos, so the workspace factory opts in with LangChain TodoListMiddleware (opt-in since deepagents 0.7). Plan mode is hard read-only by replacing FilesystemMiddleware with a tools allowlist (ls / read_file / glob / grep only) — deepagents 0.7 does not support FilesystemPermission together with a shell-capable backend. Explore always uses that read-only filesystem stack; research uses it in plan mode.
Implementation layout¶
| Module | Role |
|---|---|
deep_agents/factory.py |
Build workspace and improvement Deep Agents |
deep_agents/turn_prepare.py |
Pre-turn compaction + checkpoint-first message input |
deep_agents/llm_chat.py |
ChatOpenAI + thinking / reasoning_content shim |
agent/tool_transcripts.py |
Persisted tool traces for UI replay (wiki_tool_traces_v1) |
deep_agents/constants.py |
STALE_RUNNING_SECONDS (sync with frontend) |
Remaining gaps: Tech debt — Project agents.
Project folder¶
{project_id}/
AGENTS.md
.gitignore # ignores .openkms/skills/, conversation_history/, large_tool_results/
.openkms/skills/ # installed skills (e.g. openkms/)
.git/ # optional
Runtime settings (web search, git identity, installed_skills) live in projects.settings (PostgreSQL).
The project agent system prompt includes the project name, slug, description, workspace scope (paths relative to project root), installed skills under .openkms/skills/, and the contents of AGENTS.md. SkillsMiddleware loads SKILL.md from each installed skill (parent source path .openkms/skills/).
Permissions¶
projects:read— list projects/skills, read files, chat readprojects:write— create/update/delete projects, upload skills, agent messages, git, install skills
Feature toggle: agents (Console → Feature toggles).
Git¶
- Local: init, status, log, add, commit via the files rail. The git branch button opens a Source Control view modelled on VS Code: branch pill with
ahead/behindand pull/push, a commit message box at the top (primary Commit for what is staged, with a Commit All / Commit & Push dropdown), Staged Changes and Changes groups with per-file stage / unstage / discard, a unified diff in the viewer pane (untracked files show as whole-file additions), and a commit list with ref badges. Toggle back to the file tree with the same button or All files. Agent shell (execute) can still run git in the project folder. - Remote: HTTPS + PAT only; credentials in Profile → Git credentials. On create project, optional repository URL, branch, and credential clone into the workspace. Project settings → General shows the live
originURL and current branch, lets you change origin (GitHub → GitLab and similar), and pull/push with a saved credential. Clone / pull / push APIs on/api/projects/{id}/git/*. Saving origin initializes git if needed and storessettings.git.remote_url(and optionalcredential_id).
See API reference — Projects and API reference — Agent skills.