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
29 changes: 28 additions & 1 deletion registry/coder/modules/agentapi/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ The AgentAPI module is a building block for modules that need to run an AgentAPI
```tf
module "agentapi" {
source = "registry.coder.com/coder/agentapi/coder"
version = "2.1.1"
version = "2.2.0"

agent_id = var.agent_id
web_app_slug = local.app_slug
Expand Down Expand Up @@ -62,6 +62,33 @@ module "agentapi" {
}
```

## State Persistence

AgentAPI can save and restore conversation state across workspace restarts.
This is disabled by default and requires agentapi binary >= v0.12.0.

State and PID files are stored in `$HOME/<module_dir_name>/` alongside other
module files (e.g. `$HOME/.claude-module/agentapi-state.json`).

To enable:

```tf
module "agentapi" {
# ... other config
enable_state_persistence = true
}
```

To override file paths:

```tf
module "agentapi" {
# ... other config
state_file_path = "/custom/path/state.json"
pid_file_path = "/custom/path/agentapi.pid"
}
```

## For module developers

For a complete example of how to use this module, see the [Goose module](https://github.com/coder/registry/blob/main/registry/coder/modules/goose/main.tf).
108 changes: 108 additions & 0 deletions registry/coder/modules/agentapi/agentapi.tftest.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
mock_provider "coder" {}

variables {
agent_id = "test-agent"
web_app_icon = "/icon/test.svg"
web_app_display_name = "Test"
web_app_slug = "test"
cli_app_display_name = "Test CLI"
cli_app_slug = "test-cli"
start_script = "echo test"
module_dir_name = ".test-module"
}

run "default_values" {
command = plan

assert {
condition = var.enable_state_persistence == false
error_message = "enable_state_persistence should default to false"
}

assert {
condition = var.state_file_path == ""
error_message = "state_file_path should default to empty string"
}

assert {
condition = var.pid_file_path == ""
error_message = "pid_file_path should default to empty string"
}

# Verify start script contains state persistence ARG_ vars.
assert {
condition = can(regex("ARG_ENABLE_STATE_PERSISTENCE", coder_script.agentapi.script))
error_message = "start script should contain ARG_ENABLE_STATE_PERSISTENCE"
}

assert {
condition = can(regex("ARG_STATE_FILE_PATH", coder_script.agentapi.script))
error_message = "start script should contain ARG_STATE_FILE_PATH"
}

assert {
condition = can(regex("ARG_PID_FILE_PATH", coder_script.agentapi.script))
error_message = "start script should contain ARG_PID_FILE_PATH"
}

# Verify shutdown script contains PID-related ARG_ vars.
assert {
condition = can(regex("ARG_PID_FILE_PATH", coder_script.agentapi_shutdown.script))
error_message = "shutdown script should contain ARG_PID_FILE_PATH"
}

assert {
condition = can(regex("ARG_MODULE_DIR_NAME", coder_script.agentapi_shutdown.script))
error_message = "shutdown script should contain ARG_MODULE_DIR_NAME"
}

assert {
condition = can(regex("ARG_ENABLE_STATE_PERSISTENCE", coder_script.agentapi_shutdown.script))
error_message = "shutdown script should contain ARG_ENABLE_STATE_PERSISTENCE"
}
}

run "state_persistence_disabled" {
command = plan

variables {
enable_state_persistence = false
}

assert {
condition = var.enable_state_persistence == false
error_message = "enable_state_persistence should be false"
}

# Even when disabled, the ARG_ vars should still be in the script
# (the shell script handles the conditional logic).
assert {
condition = can(regex("ARG_ENABLE_STATE_PERSISTENCE='false'", coder_script.agentapi.script))
error_message = "start script should contain ARG_ENABLE_STATE_PERSISTENCE='false'"
}
}

run "custom_paths" {
command = plan

variables {
state_file_path = "/custom/state.json"
pid_file_path = "/custom/agentapi.pid"
}

assert {
condition = can(regex("/custom/state.json", coder_script.agentapi.script))
error_message = "start script should contain custom state_file_path"
}

assert {
condition = can(regex("/custom/agentapi.pid", coder_script.agentapi.script))
error_message = "start script should contain custom pid_file_path"
}

# Verify custom paths also appear in shutdown script.
assert {
condition = can(regex("/custom/agentapi.pid", coder_script.agentapi_shutdown.script))
error_message = "shutdown script should contain custom pid_file_path"
}
}
Loading