Skip to content

Config

pycodeloop.core.config.Config

Import

You can import the Config class with:

from pycodeloop import Config
from pycodeloop.providers import GenericProvider
Example

class pycodeloop.core.config.Config

config = Config(
    provider=GenericProvider.from_json("path/to/config.json"),
)

Parameters:

Name Type Description Default
provider Optional[Provider]

LLM backend driving the agent — always a GenericProvider under the hood. Defaults to the provider named by the PYCODELOOP_PROVIDER env var (a path to a JSON config file, a bare "generic" name, or a 'module.path:ClassName'; a bundled Anthropic config when unset).

None
tools Optional[List[Tool]]

Tools exposed to the agent. Defaults to the built-in read/write/edit/grep/bash set.

None
system_prompt Optional[str]

Overrides the default system prompt.

None
max_turns int

Hard cap on tool-use loop iterations.

MAX_TURNS
max_history_turns Optional[int]

Cap the session on the number of most recent user-initiated turns kept — older turns are dropped as a whole unit (never mid tool_calls/ tool_result) before each provider call. Defaults to 20; pass None to let the session grow without bound instead.

20
skills bool

Discover Claude Code skills/memory, Cursor rules, and AGENTS.md files on this machine and this project, expose them as a read_skill tool, and list them in the system prompt so the agent knows what's available.

False
skill_sources Optional[Set[str]]

Limit discovery to these sources ("claude-skill", "claude-memory", "cursor-rule", "agents-md"). Defaults to all of them.

None
skills_refresh bool

Skip the ~/.pycodeloop/config.json skills cache and force a full rescan.

False
delegation bool

Expose a delegate tool that spawns a fresh sub-agent (same provider, read-only tools — no write/edit/ delete/bash) for an independent subtask. Multiple delegate calls in one turn run in parallel, same as any other same-name, non-dangerous tool calls. Off by default.

False
memory bool

Load .pycodeloop/memory.md (if present) into the system prompt and expose a remember tool the agent uses to save standing corrections/preferences there — so a rule given in one session is still followed in the next, instead of the user repeating it every time. On by default.

True
trace bool

Append one JSON line per provider call, tool call/ result, retry, and compaction event to ~/.pycodeloop/logs/<session_key or "global">.jsonl, for postmortem debugging when a run misbehaves. On by default.

True
storage Optional[Sessions]

Persists the session so CodeLoop.run(prompt, session_key=...) can resume a conversation across process restarts. Defaults to SqliteSessions() (~/.pycodeloop/pycodeloop.db). Pass False to keep sessions in memory only, for the life of the CodeLoop instance.

None
workspace bool

Jail read_file/write_file/edit_file/ delete_file/grep/glob to the process's working directory — an absolute path or .. escape outside it is refused. Does NOT cover bash/git, which run arbitrary shell/subprocess commands with no path parsing; their only guardrail is the dangerous confirm gate. On by default; set False only for a trusted, single-project setup that needs tools to read/write outside the workspace (e.g. a shared config directory).

True

Attributes:

Name Type Description
provider Provider
tools List[Tool]
system_prompt Optional[str]
max_turns int
skills List[Skill]
storage Optional[Sessions]
Source code in pycodeloop/core/config.py
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
class Config:
    """
    Import:
        You can import the **Config** class with:

            from pycodeloop import Config
            from pycodeloop.providers import GenericProvider

    Example:
        `class` pycodeloop.core.config.Config

            config = Config(
                provider=GenericProvider.from_json("path/to/config.json"),
            )

    Args:
        provider (Optional[Provider]): LLM backend driving the agent —
            always a `GenericProvider` under the hood. Defaults to the
            provider named by the `PYCODELOOP_PROVIDER` env var (a
            path to a JSON config file, a bare `"generic"` name, or a
            `'module.path:ClassName'`; a bundled Anthropic config when
            unset).

        tools (Optional[List[Tool]]): Tools exposed to the agent.
            Defaults to the built-in read/write/edit/grep/bash set.

        system_prompt (Optional[str]): Overrides the default system
            prompt.

        max_turns (int): Hard cap on tool-use loop iterations.

        max_history_turns (Optional[int]): Cap the session on the
            number of most recent user-initiated turns kept — older
            turns are dropped as a whole unit (never mid tool_calls/
            tool_result) before each provider call. Defaults to `20`;
            pass `None` to let the session grow without bound instead.

        skills (bool): Discover Claude Code skills/memory, Cursor rules,
            and AGENTS.md files on this machine and this project, expose
            them as a `read_skill` tool, and list them in the system
            prompt so the agent knows what's available.

        skill_sources (Optional[Set[str]]): Limit discovery to these
            sources ("claude-skill", "claude-memory", "cursor-rule",
            "agents-md"). Defaults to all of them.

        skills_refresh (bool): Skip the `~/.pycodeloop/config.json` skills
            cache and force a full rescan.

        delegation (bool): Expose a `delegate` tool that spawns a fresh
            sub-agent (same provider, read-only tools — no write/edit/
            delete/bash) for an independent subtask. Multiple `delegate`
            calls in one turn run in parallel, same as any other
            same-name, non-dangerous tool calls. Off by default.

        memory (bool): Load `.pycodeloop/memory.md` (if present) into the
            system prompt and expose a `remember` tool the agent uses to
            save standing corrections/preferences there — so a rule given
            in one session is still followed in the next, instead of the
            user repeating it every time. On by default.

        trace (bool): Append one JSON line per provider call, tool call/
            result, retry, and compaction event to
            `~/.pycodeloop/logs/<session_key or "global">.jsonl`, for
            postmortem debugging when a run misbehaves. On by default.

        storage (Optional[Sessions]): Persists the session so
            `CodeLoop.run(prompt, session_key=...)` can resume a
            conversation across process restarts. Defaults to
            `SqliteSessions()` (`~/.pycodeloop/pycodeloop.db`). Pass `False`
            to keep sessions in memory only, for the life of the
            `CodeLoop` instance.

        workspace (bool): Jail read_file/write_file/edit_file/
            delete_file/grep/glob to the process's working directory —
            an absolute path or `..` escape outside it is refused. Does
            NOT cover `bash`/`git`, which run arbitrary shell/subprocess
            commands with no path parsing; their only guardrail is the
            `dangerous` confirm gate. On by default; set `False` only
            for a trusted, single-project setup that needs tools to
            read/write outside the workspace (e.g. a shared config
            directory).

    Attributes:
        provider (Provider):
        tools (List[Tool]):
        system_prompt (Optional[str]):
        max_turns (int):
        skills (List[Skill]):
        storage (Optional[Sessions]):
    """

    _PROVIDERS = {"provider": Provider, "storage": Sessions}

    def __init__(
        self,
        provider: Provider | None = None,
        tools: list[Tool] | None = None,
        system_prompt: str | None = None,
        max_turns: int = Settings.MAX_TURNS,
        max_history_turns: int | None = 20,
        skills: bool = False,
        skill_sources: set[str] | None = None,
        skills_refresh: bool = False,
        delegation: bool = False,
        memory: bool = True,
        trace: bool = True,
        storage: Sessions | bool | None = None,
        workspace: bool = True,
    ) -> None:
        self.provider = (
            provider if provider is not None else _default_provider()
        )
        self.tools = list(tools) if tools is not None else list(DEFAULT_TOOLS)
        self.system_prompt = system_prompt
        self.max_turns = max_turns
        self.max_history_turns = max_history_turns
        self.workspace = workspace
        set_workspace_enabled(workspace)
        self.skills = self._discover_skills(
            skills, skill_sources, skills_refresh
        )
        if delegation:
            self.tools = [
                *self.tools,
                DelegateTool(
                    provider=self.provider, tools=list(READ_ONLY_TOOLS)
                ),
            ]
        if memory:
            self._load_memory()
        self.trace = trace
        self.storage = (
            None if storage is False else storage or _default_storage()
        )

        self._validate()

    def _append_to_system_prompt(self, text: str) -> None:
        base_prompt = (
            self.system_prompt
            if self.system_prompt is not None
            else DEFAULT_SYSTEM_PROMPT
        )
        self.system_prompt = f"{base_prompt}\n\n{text}"

    def _discover_skills(
        self, enabled: bool, sources: set[str] | None, refresh: bool
    ) -> list:
        if not enabled:
            return []

        found = discover_skills(sources=sources, use_cache=not refresh)

        if not found:
            return found

        self.tools = [*self.tools, ReadSkillTool(found)]
        self._append_to_system_prompt(render_skills_index(found))
        return found

    def _load_memory(self) -> None:
        self.tools = [*self.tools, RememberTool()]

        content = load_memory()
        if not content:
            return

        self._append_to_system_prompt(render_memory_prompt(content))

    def _validate(self) -> None:
        for name, abc in self._PROVIDERS.items():
            value = getattr(self, name)

            if value is not None and not isinstance(value, abc):
                raise NotProviderInstance(name=name)

max_history_turns = max_history_turns instance-attribute

max_turns = max_turns instance-attribute

provider = provider if provider is not None else _default_provider() instance-attribute

skills = self._discover_skills(skills, skill_sources, skills_refresh) instance-attribute

storage = None if storage is False else storage or _default_storage() instance-attribute

system_prompt = system_prompt instance-attribute

tools = list(tools) if tools is not None else list(DEFAULT_TOOLS) instance-attribute

trace = trace instance-attribute

workspace = workspace instance-attribute

__init__(provider=None, tools=None, system_prompt=None, max_turns=Settings.MAX_TURNS, max_history_turns=20, skills=False, skill_sources=None, skills_refresh=False, delegation=False, memory=True, trace=True, storage=None, workspace=True)

Source code in pycodeloop/core/config.py
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
def __init__(
    self,
    provider: Provider | None = None,
    tools: list[Tool] | None = None,
    system_prompt: str | None = None,
    max_turns: int = Settings.MAX_TURNS,
    max_history_turns: int | None = 20,
    skills: bool = False,
    skill_sources: set[str] | None = None,
    skills_refresh: bool = False,
    delegation: bool = False,
    memory: bool = True,
    trace: bool = True,
    storage: Sessions | bool | None = None,
    workspace: bool = True,
) -> None:
    self.provider = (
        provider if provider is not None else _default_provider()
    )
    self.tools = list(tools) if tools is not None else list(DEFAULT_TOOLS)
    self.system_prompt = system_prompt
    self.max_turns = max_turns
    self.max_history_turns = max_history_turns
    self.workspace = workspace
    set_workspace_enabled(workspace)
    self.skills = self._discover_skills(
        skills, skill_sources, skills_refresh
    )
    if delegation:
        self.tools = [
            *self.tools,
            DelegateTool(
                provider=self.provider, tools=list(READ_ONLY_TOOLS)
            ),
        ]
    if memory:
        self._load_memory()
    self.trace = trace
    self.storage = (
        None if storage is False else storage or _default_storage()
    )

    self._validate()