kiro: wire session CWD through to EnvState; o: add new command

- backend: add CWD field to GenerationParams for environment state
- kiro: pass session CWD to kiroCurrentEnvState instead of hardcoded '/'
- agent/runtime: set GenParams.CWD from session cwd in BuildRuntime
- o new: create sessions and agents via 9P filesystem
- o prompt: support piped stdin for non-interactive use
This commit is contained in:
Levi Neely 2026-08-10 09:10:13 +02:00
parent fbe8059cf5
commit 46524d37dd
4 changed files with 55 additions and 12 deletions

View File

@ -174,6 +174,7 @@ func BuildRuntime(cfg *AgentConfig, srv *toolsrv.Conn, cwd string, env []string,
genParams = cfg.GenerationParams
maxSteps = cfg.MaxSteps
}
genParams.CWD = cwd
exec := func(ctx context.Context, name string, args json.RawMessage) (string, []backend.ContentBlock, error) {
if srv == nil {

View File

@ -140,6 +140,10 @@ type GenerationParams struct {
IncludeReasoning *bool `json:"includeReasoning,omitempty"`
ResponseFormat string `json:"responseFormat,omitempty"`
Stop []string `json:"stop,omitempty"`
// Environment state for backends that support it (e.g., Kiro).
// These are passed through to the backend API but not used for sampling.
CWD string `json:"-"` // Current working directory
}
// Backend is the interface all LLM providers must implement.

View File

@ -315,7 +315,7 @@ func buildKiroRequest(model string, messages []Message, tools []Tool, profileARN
if err != nil {
return nil, err
}
current = kiroChatMessage{UserInputMessage: encodeKiroUserMessage(messages[len(messages)-1], model, encodedTools, nil, true)}
current = kiroChatMessage{UserInputMessage: encodeKiroUserMessage(messages[len(messages)-1], model, encodedTools, nil, params.CWD)}
case "tool":
// Find the last non-tool message.
@ -328,7 +328,7 @@ func buildKiroRequest(model string, messages []Message, tools []Tool, profileARN
return nil, err
}
toolResults, images := encodeKiroToolResults(messages[cutoff+1:])
current = kiroChatMessage{UserInputMessage: emptyKiroUserMessage(model, encodedTools, toolResults, images, true)}
current = kiroChatMessage{UserInputMessage: emptyKiroUserMessage(model, encodedTools, toolResults, images, params.CWD)}
default:
return nil, fmt.Errorf("last message role %q is not supported (expected user, system, or tool)", lastRole)
@ -363,7 +363,7 @@ func encodeKiroHistory(messages []Message) ([]kiroChatMessage, error) {
m := messages[i]
switch strings.ToLower(strings.TrimSpace(m.Role)) {
case "user", "system":
out = append(out, kiroChatMessage{UserInputMessage: encodeKiroUserMessage(m, "", nil, nil, false)})
out = append(out, kiroChatMessage{UserInputMessage: encodeKiroUserMessage(m, "", nil, nil, "")})
case "assistant":
out = append(out, kiroChatMessage{AssistantResponseMessage: encodeKiroAssistantMessage(m)})
case "tool":
@ -373,13 +373,13 @@ func encodeKiroHistory(messages []Message) ([]kiroChatMessage, error) {
i++
}
results, imgs := encodeKiroToolResults(messages[start : i+1])
out = append(out, kiroChatMessage{UserInputMessage: emptyKiroUserMessage("", nil, results, imgs, false)})
out = append(out, kiroChatMessage{UserInputMessage: emptyKiroUserMessage("", nil, results, imgs, "")})
}
}
return out, nil
}
func encodeKiroUserMessage(m Message, modelID string, tools []kiroTool, toolResults []kiroToolResult, includeEnvState bool) *kiroUserInputMessage {
func encodeKiroUserMessage(m Message, modelID string, tools []kiroTool, toolResults []kiroToolResult, cwd string) *kiroUserInputMessage {
msg := &kiroUserInputMessage{
Content: kiroExtractTextContent(m),
Origin: kiroDefaultOrigin,
@ -393,8 +393,8 @@ func encodeKiroUserMessage(m Message, modelID string, tools []kiroTool, toolResu
if len(toolResults) > 0 {
ctx.ToolResults = toolResults
}
if includeEnvState {
ctx.EnvState = kiroCurrentEnvState()
if cwd != "" {
ctx.EnvState = kiroCurrentEnvState(cwd)
}
if len(ctx.Tools) > 0 || len(ctx.ToolResults) > 0 || ctx.EnvState != nil {
msg.UserInputMessageContext = ctx
@ -402,7 +402,7 @@ func encodeKiroUserMessage(m Message, modelID string, tools []kiroTool, toolResu
return msg
}
func emptyKiroUserMessage(modelID string, tools []kiroTool, toolResults []kiroToolResult, images []kiroImageBlock, includeEnvState bool) *kiroUserInputMessage {
func emptyKiroUserMessage(modelID string, tools []kiroTool, toolResults []kiroToolResult, images []kiroImageBlock, cwd string) *kiroUserInputMessage {
msg := &kiroUserInputMessage{
Content: "",
Origin: kiroDefaultOrigin,
@ -416,8 +416,8 @@ func emptyKiroUserMessage(modelID string, tools []kiroTool, toolResults []kiroTo
if len(toolResults) > 0 {
ctx.ToolResults = toolResults
}
if includeEnvState {
ctx.EnvState = kiroCurrentEnvState()
if cwd != "" {
ctx.EnvState = kiroCurrentEnvState(cwd)
}
if len(ctx.Tools) > 0 || len(ctx.ToolResults) > 0 || ctx.EnvState != nil {
msg.UserInputMessageContext = ctx
@ -564,10 +564,10 @@ func kiroExtractImages(m Message) []kiroImageBlock {
return images
}
func kiroCurrentEnvState() *kiroEnvState {
func kiroCurrentEnvState(cwd string) *kiroEnvState {
return &kiroEnvState{
OperatingSystem: runtime.GOOS,
CurrentWorkingDirectory: "/",
CurrentWorkingDirectory: cwd,
}
}

View File

@ -12,6 +12,7 @@
# read <path> Read a file (-l to loop)
# write <path> [data] Write to a file (args or stdin)
# ls [path] List namespace entries
# new <session> [agent] [cwd] Create a new session or agent
# ctl <command> Send a control command (stop, compact, kill, ...)
# prompt Interactive REPL for submitting prompts (requires agent)
# chatstream Stream chat output (requires agent)
@ -280,6 +281,13 @@ cmd_prompt() {
fi
local path="session/$s/agent/$a"
# If stdin is not a terminal, read it all and send as a single prompt
if [[ ! -t 0 ]]; then
cat | ollie-9p write "$path/prompt"
return
fi
# Interactive REPL mode
echo "[$s/$a]"
echo "Type . on a blank line to send, Ctrl+D to exit."
echo "/ prefix runs ctl: /stop, /compact, /model X, /inject X"
@ -386,6 +394,30 @@ cmd_env() {
fi
}
cmd_new() {
if [[ $# -lt 1 ]]; then
echo "usage: o new <session> [agent] [cwd]" >&2
echo " o new myproject Create a new session named 'myproject'" >&2
echo " o new myproject coding /path Create agent 'coding' in session 'myproject'" >&2
exit 1
fi
local session_name="$1"
shift
if [[ $# -eq 0 ]]; then
# Create new session
printf 'name=%s\ncwd=%s\n' "$session_name" "$PWD" | ollie-9p write "session/new"
echo "created session: $session_name"
else
# Create new agent in existing session
local agent_name="$1"
local agent_cwd="${2:-$PWD}"
printf 'name=%s\ncwd=%s\n' "$agent_name" "$agent_cwd" | ollie-9p write "session/$session_name/agent/new"
echo "created agent: $session_name/$agent_name"
fi
}
cmd_tui() {
local tmux_session="${session:-}"
local s a
@ -483,6 +515,9 @@ Commands:
write <path> [data] Write to a file (args or stdin)
ls [path] List namespace entries
new <session> Create a new session
new <session> <agent> [cwd] Create an agent in a session
generate <prompt> One-shot generation (no session needed)
ctl <cmd> [args] Send control command (rdwr: gets response)
@ -506,6 +541,8 @@ Examples:
o read -l statewait # block-loop on state changes
o read chat # stream chat (filtered text)
o read chat.raw # stream chat (full markup)
o new myproject # create session 'myproject' in current dir
o new myproject coding # create agent 'coding' in 'myproject'
o generate "explain monads" # one-shot, no session
o ctl stop # stop agent (uses $session/$agent)
o ctl kill mysession # kill session by name
@ -529,6 +566,7 @@ case "$subcmd" in
ctl) cmd_ctl "$@" ;;
prompt) cmd_prompt "$@" ;;
log) cmd_log "$@" ;;
new) cmd_new "$@" ;;
tui) cmd_tui "$@" ;;
env) cmd_env "$@" ;;
help|-h|--help) cmd_help ;;