When a workflow execution agent runs execute_shell_command, the working directory
it defaults to is determined by a session-scoped config map. This doc explains
how that plumbing works and where the working directory is set at each stage.
There are two distinct session IDs in the system:
| ID | Created | Stored |
|---|---|---|
| HTTP session ID | From X-Session-ID request header |
sessionID in server.go |
| MCP session ID | workflow-session-{timestamp} in workflow_orchestrator.go |
hcpo.sessionID |
The session working dir config map (common.sessionShellConfigs) is keyed by the HTTP session ID.
execute_shell_command.go reads ChatSessionIDKey from context to find the session:
ctx → ChatSessionIDKey → sessionID → GetSessionShellConfig(sessionID) → WorkingDir
Priority order when no explicit working_directory arg is passed:
sessionCfg.WorkingDir(session map, keyed byChatSessionIDKeyin context)c.DefaultWorkingDir(workspace client field — typically empty)c.ExtraEnv["_DEFAULT_WORKING_DIR"]- Empty → workspace root
The workflow execution runs in a fresh context (context.Background()):
workflowCtx = context.WithCancel(context.Background())
workflowCtx = context.WithValue(workflowCtx, common.UserIDKey, currentUserID)
workflowCtx = context.WithValue(workflowCtx, common.ChatSessionIDKey, sessionID) // injects HTTP session IDThe ChatSessionIDKey injection (third line) is critical — without it, all execution
agent shell commands would fall through to workspace root as the working directory,
ignoring whatever was set via SetSessionWorkingDir.
Note: The main chat agent also injects ChatSessionIDKey at server.go:5138 into its
own agentCtx. Phase chat agents use this same path.
workspace.SetSessionWorkingDir(sessionID, workflowWorkspacePath)
// e.g. "Workflow/HRMS"All shell commands at this point default to Workflow/HRMS.
When a run folder is resolved (either from frontend options or interactive selection), the session working dir is updated to the run's execution folder:
// controller.go (frontend path, line ~549)
common.SetSessionWorkingDir(hcpo.httpSessionID, "Workflow/HRMS/runs/run-001/execution")
// controller.go (interactive path, line ~605)
common.SetSessionWorkingDir(hcpo.httpSessionID, "Workflow/HRMS/runs/run-001/execution")
// execution_manager.go (batch execution path, applyExecutionContext)
common.SetSessionWorkingDir(orch.httpSessionID, "Workflow/HRMS/runs/run-001/execution")After this point, all shell commands (execution agents, learning agents, and todo-task agents)
default to Workflow/HRMS/runs/run-001/execution.
These are separate mechanisms:
- Working directory: where relative paths and
./resolve for shell commands - Folder guard: which paths an agent is allowed to read/write (enforced via Isolator)
The folder guard for execution agents is set per-step via SetWorkspacePathForFolderGuard
(snapshotted at agent creation time in WrapWorkspaceToolsWithFolderGuard). It is narrower
than the working directory — e.g., write access restricted to execution/step-3/.
The working directory is broader — the whole execution/ folder — so the agent can read
previous step outputs with relative paths like ../step-2/output.json.
The interactive workshop (workflow builder) also runs in workflowCtx and inherits the
ChatSessionIDKey. At build time, selectedRunFolder is not yet set, so the working
directory remains Workflow/HRMS — correct for a builder that reads/writes planning files.
| Stage | Working Dir | How set |
|---|---|---|
| Workflow start | Workflow/HRMS |
SetSessionWorkingDir in server.go:2771 |
| After run folder selected | Workflow/HRMS/runs/run-001/execution |
common.SetSessionWorkingDir in controller.go / execution_manager.go |
| Chat agent | Chats/ |
DefaultWorkingDirKey context injection in tool_setup.go |
| Phase chat agent | phaseWorkspacePath |
SetSessionWorkingDir in server.go:4472 |
| Plans agent | Chats/ |
SetSessionWorkingDir in server.go:3756 |