Skip to content
Draft
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
3 changes: 2 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ OPENROUTER_API_KEY=
# Optional: API key required when the control plane enforces auth
AGENTFIELD_API_KEY=

# Optional model/provider overrides
# Optional model/provider overrides (`aforge` uses the unreleased aforge-v2 CLI)
HARNESS_PROVIDER=opencode
HARNESS_MODEL=openrouter/moonshotai/kimi-k2.5
AI_MODEL=openrouter/moonshotai/kimi-k2.5
# SEC_AF_AFORGE_BIN=/absolute/path/to/aforge

# Optional: host path with repositories to scan/mirror into /workspaces
# Example: SCAN_REPOS_PATH=../repos
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ COPY pyproject.toml README.md ./
COPY src/ src/

RUN pip install --no-cache-dir --prefix=/install \
"agentfield>=0.1.0" \
"agentfield @ git+https://github.com/Agent-Field/agentfield.git@60b0aa76df604575429af60fb0ccffc9aaca7745#subdirectory=sdk/python" \
"pydantic>=2.0" \
"httpx>=0.27" \
"python-dotenv>=1.0" && \
Expand Down
22 changes: 20 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ The pipeline adapts at runtime based on what it discovers. An AI gate examines r

**6. Guided autonomy for coding agents**

SEC-AF runs on top of coding agents (Claude Code, OpenCode, Codex) via the AgentField harness. Rather than giving the agent a single massive prompt, each reasoner provides phase-aware guided autonomy: the agent receives a narrow task definition, a flat output schema (2-4 fields), and strategy-specific context. The agent has full autonomy within these boundaries — it can read files, trace code, and reason freely — but the harness constrains the _shape_ of its output. This prevents the common failure mode where autonomous agents go off-task or produce unstructured results.
SEC-AF runs on top of coding agents (Aforge, Claude Code, OpenCode, Codex) via the AgentField harness. Rather than giving the agent a single massive prompt, each reasoner provides phase-aware guided autonomy: the agent receives a narrow task definition, a flat output schema (2-4 fields), and strategy-specific context. The agent has full autonomy within these boundaries — it can read files, trace code, and reason freely — but the harness constrains the _shape_ of its output. This prevents the common failure mode where autonomous agents go off-task or produce unstructured results.

**7. Composable reasoner DAG with full observability**

Expand Down Expand Up @@ -477,13 +477,31 @@ jobs:
| `SEC_AF_MAX_TURNS` | No | `50` | Max harness turns per call |
| `AGENTFIELD_API_KEY` | No | unset | API key for secured environments |
| `SEC_AF_WORKSPACES_DIR` | No | `/workspaces` | Directory for cloned repos (falls back to `~/.sec-af/workspaces` if not writable) |
| `HARNESS_PROVIDER` | No | `opencode` | Harness backend provider |
| `HARNESS_PROVIDER` | No | `opencode` | Harness backend provider (`aforge`, `claude-code`, `codex`, `gemini`, or `opencode`) |
| `SEC_AF_AFORGE_BIN` | No | `aforge` | Path to an aforge-v2 binary for the `aforge` provider |
| `SEC_AF_AI_MAX_RETRIES` | No | `3` | Retry count for model calls |

</details>

## Development Setup

### Benchmark with unreleased aforge-v2

```bash
cd /path/to/aforge-v2
go build -o /absolute/path/to/bin/aforge ./cmd/aforge

export OPENROUTER_API_KEY=sk-or-v1-...
export HARNESS_PROVIDER=aforge
export SEC_AF_AFORGE_BIN=/absolute/path/to/bin/aforge
python -m sec_af.app
```

This uses the same SEC-AF prompts and schemas as OpenCode; only the AgentField
harness provider changes. The draft pins the exact AgentField harness commit
used for benchmarking; replace that pin with the released SDK before merging.
The current Docker image does not bundle the unreleased Aforge binary.

```bash
python -m venv .venv && source .venv/bin/activate
pip install -e .[dev]
Expand Down
4 changes: 3 additions & 1 deletion agentfield-package.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,10 @@ user_environment:
type: secret
scope: global
- name: HARNESS_PROVIDER
description: Coding-agent harness provider
description: Coding-agent harness provider (aforge | claude-code | codex | gemini | opencode)
default: opencode
- name: SEC_AF_AFORGE_BIN
description: Optional path to the aforge-v2 binary (defaults to aforge on PATH)
- name: HARNESS_MODEL
description: Model the harness uses
default: openrouter/moonshotai/kimi-k2.5
Expand Down
2 changes: 1 addition & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ services:
- AGENTFIELD_API_KEY=${AGENTFIELD_API_KEY:-}
- PORT=8003
- AGENT_CALLBACK_URL=http://sec-af:8003
- HARNESS_PROVIDER=opencode
- HARNESS_PROVIDER=${HARNESS_PROVIDER:-opencode}
- HARNESS_MODEL=${HARNESS_MODEL:-openrouter/moonshotai/kimi-k2.5}
- AI_MODEL=${AI_MODEL:-openrouter/moonshotai/kimi-k2.5}
- OPENROUTER_API_KEY=${OPENROUTER_API_KEY}
Expand Down
5 changes: 4 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ license = "Apache-2.0"
requires-python = ">=3.11"
authors = [{ name = "AgentField", email = "hello@agentfield.dev" }]
dependencies = [
"agentfield>=0.1.0",
"agentfield @ git+https://github.com/Agent-Field/agentfield.git@60b0aa76df604575429af60fb0ccffc9aaca7745#subdirectory=sdk/python",
"pydantic>=2.0",
"httpx>=0.27",
]
Expand All @@ -31,6 +31,9 @@ sec-af = "sec_af.app:main"
[tool.hatch.build.targets.wheel]
packages = ["src/sec_af"]

[tool.hatch.metadata]
allow-direct-references = true

[tool.ruff]
target-version = "py311"
line-length = 120
Expand Down
1 change: 1 addition & 0 deletions src/sec_af/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
max_turns=_ai_config.max_turns,
env=_ai_config.provider_env(),
opencode_bin=_ai_config.opencode_bin,
aforge_bin=_ai_config.aforge_bin,
permission_mode="auto",
),
ai_config=AIConfig(
Expand Down
6 changes: 6 additions & 0 deletions src/sec_af/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,12 @@ class AIIntegrationConfig(BaseModel):
)
max_backoff_seconds: float = Field(default_factory=lambda: float(os.getenv("SEC_AF_AI_MAX_BACKOFF_SECONDS", "8.0")))
opencode_bin: str = Field(default_factory=lambda: os.getenv("SEC_AF_OPENCODE_BIN", "opencode"))
aforge_bin: str = Field(
default_factory=lambda: os.getenv(
"SEC_AF_AFORGE_BIN",
os.getenv("AFORGE_BIN", "aforge"),
)
)
opencode_server: str | None = Field(
default_factory=lambda: os.getenv("SEC_AF_OPENCODE_SERVER", os.getenv("OPENCODE_SERVER")),
)
Expand Down
5 changes: 5 additions & 0 deletions tests/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ def test_ai_integration_config_uses_sec_af_env_precedence(monkeypatch: pytest.Mo
monkeypatch.setenv("SEC_AF_AI_INITIAL_BACKOFF_SECONDS", "1.5")
monkeypatch.setenv("SEC_AF_AI_MAX_BACKOFF_SECONDS", "12")
monkeypatch.setenv("SEC_AF_OPENCODE_BIN", "/usr/local/bin/opencode")
monkeypatch.setenv("SEC_AF_AFORGE_BIN", "/usr/local/bin/aforge")

config = AIIntegrationConfig.from_env()

Expand All @@ -73,6 +74,7 @@ def test_ai_integration_config_uses_sec_af_env_precedence(monkeypatch: pytest.Mo
assert config.initial_backoff_seconds == 1.5
assert config.max_backoff_seconds == 12
assert config.opencode_bin == "/usr/local/bin/opencode"
assert config.aforge_bin == "/usr/local/bin/aforge"


def test_ai_integration_config_falls_back_to_harness_and_defaults(monkeypatch: pytest.MonkeyPatch) -> None:
Expand All @@ -88,6 +90,8 @@ def test_ai_integration_config_falls_back_to_harness_and_defaults(monkeypatch: p
"SEC_AF_AI_INITIAL_BACKOFF_SECONDS",
"SEC_AF_AI_MAX_BACKOFF_SECONDS",
"SEC_AF_OPENCODE_BIN",
"SEC_AF_AFORGE_BIN",
"AFORGE_BIN",
):
monkeypatch.delenv(key, raising=False)

Expand All @@ -101,6 +105,7 @@ def test_ai_integration_config_falls_back_to_harness_and_defaults(monkeypatch: p
assert config.initial_backoff_seconds == 2.0
assert config.max_backoff_seconds == 8.0
assert config.opencode_bin == "opencode"
assert config.aforge_bin == "aforge"


def test_provider_env_only_includes_present_keys(monkeypatch: pytest.MonkeyPatch) -> None:
Expand Down