Skip to content
Merged
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