Skip to content
Merged
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
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
## v0.8.40 - 2025-01-31

### Features
- **Autonomous Error Tracking**: Added comprehensive error activity tracking for autonomous task execution. The system now automatically creates agent activities when tasks fail, return empty responses, or encounter unexpected errors, improving error visibility and debugging capabilities.
- **Memory Management**: Added `has_memory` flag support for autonomous tasks, allowing fine-grained control over thread memory persistence per task execution.

### Bug Fixes
- **Changelog Generation**: Fixed bug in changelog generation process.

### Technical Details
- Enhanced `run_autonomous_task` function with error detection and activity creation
- Improved error handling for empty responses, system errors, and exceptions
- Added proper logging for error activity creation failures

**Full Changelog**: https://github.com/crestalnetwork/intentkit/compare/v0.8.39...v0.8.40

## v0.8.39 - 2026-01-04

### Features
Expand Down
2 changes: 1 addition & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ services:
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-postgres}
POSTGRES_DB: ${POSTGRES_DB:-intentkit}
volumes:
- postgres_data:/var/lib/postgresql/data
- ./pg_data:/var/lib/postgresql
Copy link

Copilot AI Jan 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The volume mount path has been changed from a named volume to a host directory mount. The original path was "postgres_data:/var/lib/postgresql/data" (standard PostgreSQL data directory), but it's now "./pg_data:/var/lib/postgresql" which is the parent directory. This change may cause PostgreSQL to fail to start or lose data because:

  1. The mount point should be "/var/lib/postgresql/data" not "/var/lib/postgresql"
  2. The named volume "postgres_data" is no longer defined in the volumes section

This appears to be an unintended change that's unrelated to the PR's stated purpose of adding agent visibility features.

Suggested change
- ./pg_data:/var/lib/postgresql
- postgres_data:/var/lib/postgresql/data

Copilot uses AI. Check for mistakes.
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-postgres}"]
interval: 5s
Expand Down
21 changes: 15 additions & 6 deletions intentkit/core/prompt.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@

You are integrated with the Enso API. You can use enso_get_tokens to retrieve token information,
including APY, Protocol Slug, Symbol, Address, Decimals, and underlying tokens. When interacting with token amounts,
ensure to multiply input amounts by the token's decimal places and divide output amounts by the token's decimals.
Utilize enso_route_shortcut to find the best swap or deposit route. Set broadcast_request to True only when the
user explicitly requests a transaction broadcast. Insufficient funds or insufficient spending approval can cause
Route Shortcut broadcasts to fail. To avoid this, use the enso_broadcast_wallet_approve tool that requires explicit
ensure to multiply input amounts by the token's decimal places and divide output amounts by the token's decimals.
Utilize enso_route_shortcut to find the best swap or deposit route. Set broadcast_request to True only when the
user explicitly requests a transaction broadcast. Insufficient funds or insufficient spending approval can cause
Route Shortcut broadcasts to fail. To avoid this, use the enso_broadcast_wallet_approve tool that requires explicit
user confirmation before broadcasting any approval transactions for security reasons.

"""
Expand Down Expand Up @@ -208,6 +208,7 @@ def build_agent_prompt(agent: Agent, agent_data: AgentData) -> str:
- Wallet information
- Agent characteristics (purpose, personality, principles)
- Skills-specific guides
- Extra prompt from template

Args:
agent: The agent configuration
Expand All @@ -226,7 +227,13 @@ def build_agent_prompt(agent: Agent, agent_data: AgentData) -> str:
_build_skills_guides_section(agent),
]

return "".join(section for section in prompt_sections if section)
base_prompt = "".join(section for section in prompt_sections if section)

# Add extra_prompt from template if present
if agent.extra_prompt:
base_prompt += f"## Task Details\n\n{agent.extra_prompt}\n\n"

return base_prompt


# Legacy function name for backward compatibility
Expand Down Expand Up @@ -385,6 +392,8 @@ async def build_system_prompt(

if agent.prompt_append:
processed_append = await explain_prompt(agent.prompt_append)
final_system_prompt = f"{final_system_prompt}{processed_append}"
final_system_prompt = (
f"{final_system_prompt}## Additional Instructions\n\n{processed_append}"
)

return final_system_prompt
12 changes: 11 additions & 1 deletion intentkit/core/template.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from pydantic import BaseModel
from sqlalchemy import select

from intentkit.models.agent import Agent, AgentCore, AgentTable
from intentkit.models.agent import Agent, AgentCore, AgentTable, AgentVisibility
from intentkit.models.db import get_session
from intentkit.models.template import Template, TemplateTable

Expand Down Expand Up @@ -120,6 +120,9 @@ class AgentCreationFromTemplate(BaseModel):
name: str | None = None
picture: str | None = None
description: str | None = None
readonly_wallet_address: str | None = None
weekly_spending_limit: float | None = None
extra_prompt: str | None = None
Comment on lines +123 to +125
Copy link

Copilot AI Jan 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The AgentCreationFromTemplate class is missing field descriptions. The newly added fields (readonly_wallet_address, weekly_spending_limit, extra_prompt) should have Pydantic Field descriptions to document their purpose and usage, consistent with other API models in the codebase.

Copilot uses AI. Check for mistakes.


async def create_agent_from_template(
Expand Down Expand Up @@ -158,13 +161,20 @@ async def create_agent_from_template(
if data.picture:
core_data["picture"] = data.picture

# Set visibility based on team_id
visibility = AgentVisibility.TEAM if team_id else AgentVisibility.PRIVATE

# Create new agent
db_agent = AgentTable(
id=str(XID()),
owner=owner,
team_id=team_id,
template_id=data.template_id,
description=data.description,
readonly_wallet_address=data.readonly_wallet_address,
weekly_spending_limit=data.weekly_spending_limit,
extra_prompt=data.extra_prompt,
visibility=visibility,
**core_data,
)
db.add(db_agent)
Expand Down
59 changes: 49 additions & 10 deletions intentkit/models/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import warnings
from datetime import UTC, datetime
from decimal import Decimal
from enum import IntEnum
from pathlib import Path
from typing import Annotated, Any, Literal

Expand All @@ -19,7 +20,7 @@
from pydantic import Field as PydanticField
from pydantic.json_schema import SkipJsonSchema
from pydantic.main import IncEx
from sqlalchemy import Boolean, DateTime, Float, Numeric, String, func, select
from sqlalchemy import Boolean, DateTime, Float, Integer, Numeric, String, func, select
from sqlalchemy.dialects.postgresql import JSON, JSONB
from sqlalchemy.exc import IntegrityError
from sqlalchemy.ext.asyncio import AsyncSession
Expand All @@ -43,6 +44,20 @@
)


class AgentVisibility(IntEnum):
"""Agent visibility levels with hierarchical ordering.

Higher values indicate broader visibility:
- PRIVATE (0): Only visible to owner
- TEAM (10): Visible to team members
- PUBLIC (20): Visible to everyone
"""

PRIVATE = 0
TEAM = 10
PUBLIC = 20


class AgentAutonomous(BaseModel):
"""Autonomous agent configuration."""

Expand Down Expand Up @@ -458,6 +473,17 @@ class AgentTable(Base, AgentUserInputColumns):
nullable=True,
comment="Price of the x402 request",
)
visibility: Mapped[int | None] = mapped_column(
Integer,
nullable=True,
index=True,
comment="Visibility level: 0=private, 10=team, 20=public",
)
archived_at: Mapped[datetime | None] = mapped_column(
DateTime(timezone=True),
nullable=True,
comment="Timestamp when the agent was archived. NULL means not archived",
)

# auto timestamp
created_at: Mapped[datetime] = mapped_column(
Expand Down Expand Up @@ -764,6 +790,28 @@ class AgentUpdate(AgentUserInput):
},
),
]
extra_prompt: Annotated[
str | None,
PydanticField(
default=None,
description="Only when the agent is created from a template.",
max_length=20000,
),
]
visibility: Annotated[
AgentVisibility | None,
PydanticField(
default=None,
description="Visibility level of the agent: PRIVATE(0), TEAM(10), or PUBLIC(20)",
),
]
archived_at: Annotated[
datetime | None,
PydanticField(
default=None,
description="Timestamp when the agent was archived. NULL means not archived",
),
]

@field_validator("purpose", "personality", "principles", "prompt", "prompt_append")
Copy link

Copilot AI Jan 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The extra_prompt field should be included in the field_validator that checks for level 1 and level 2 headings. Since extra_prompt is injected into the system prompt with a "## Task Details" heading (as seen in prompt.py line 234), allowing users to use # or ## headings in extra_prompt could break the prompt structure hierarchy. Add "extra_prompt" to the validator on line 816.

Suggested change
@field_validator("purpose", "personality", "principles", "prompt", "prompt_append")
@field_validator("purpose", "personality", "principles", "prompt", "prompt_append", "extra_prompt")

Copilot uses AI. Check for mistakes.
@classmethod
Expand Down Expand Up @@ -949,14 +997,6 @@ class AgentCreate(AgentUpdate):
max_length=50,
),
]
extra_prompt: Annotated[
str | None,
PydanticField(
default=None,
description="Only when the agent is created from a template.",
max_length=20000,
),
]

async def check_upstream_id(self) -> None:
if not self.upstream_id:
Expand Down Expand Up @@ -1261,7 +1301,6 @@ class Agent(AgentCreate, AgentPublicInfo):
description="Timestamp when the agent public info was last updated",
),
]

# auto timestamp
created_at: Annotated[
datetime,
Expand Down
66 changes: 65 additions & 1 deletion tests/core/test_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
create_template_from_agent,
render_agent,
)
from intentkit.models.agent import Agent, AgentTable
from intentkit.models.agent import Agent, AgentTable, AgentVisibility
from intentkit.models.template import Template, TemplateTable


Expand Down Expand Up @@ -76,6 +76,9 @@ async def mock_refresh(instance):
assert added_agent.owner == "user_1"
assert added_agent.team_id == "team_1"

# Verify visibility is set to TEAM when team_id is provided
assert added_agent.visibility == AgentVisibility.TEAM

# Verify overrides
assert added_agent.name == "New Agent"
assert added_agent.picture == "new_pic.png"
Expand All @@ -94,6 +97,67 @@ async def mock_refresh(instance):
assert agent.id == added_agent.id


@pytest.mark.asyncio
async def test_create_agent_from_template_without_team():
"""Test creating an agent from a template without team_id (PRIVATE visibility)."""

# 1. Setup Data
template_id = "template-2"
template_data = {
"id": template_id,
"name": "Template Agent",
"description": "A template agent",
"model": "gpt-4o",
"temperature": 0.5,
"prompt": "You are a template.",
}

# Mock TemplateTable instance
mock_template = TemplateTable(**template_data)

# Input data for creation
creation_data = AgentCreationFromTemplate(
template_id=template_id,
name="Private Agent",
picture="private_pic.png",
description="Created without team",
)

# 2. Mock Database
with patch("intentkit.core.template.get_session") as mock_get_session:
# Setup mock session
mock_session = MagicMock()
mock_get_session.return_value.__aenter__.return_value = mock_session

# Mock scalar return value
mock_session.scalar = AsyncMock(return_value=mock_template)
mock_session.commit = AsyncMock()

# Set timestamps on refresh
async def mock_refresh(instance):
instance.created_at = datetime.now()
instance.updated_at = datetime.now()

mock_session.refresh = AsyncMock(side_effect=mock_refresh)

# 3. Call Function without team_id
await create_agent_from_template(
data=creation_data, owner="user_2", team_id=None
)

# 4. Verify
assert mock_session.add.called
args, _ = mock_session.add.call_args
added_agent = args[0]

assert isinstance(added_agent, AgentTable)
assert added_agent.owner == "user_2"
assert added_agent.team_id is None

# Verify visibility is set to PRIVATE when team_id is None
assert added_agent.visibility == AgentVisibility.PRIVATE
Comment on lines +100 to +158
Copy link

Copilot AI Jan 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new test doesn't verify that the newly added fields (readonly_wallet_address, weekly_spending_limit, extra_prompt) from AgentCreationFromTemplate are correctly passed through to the created agent. While the test verifies visibility, it should also test these new optional fields to ensure complete coverage of the new functionality.

Copilot uses AI. Check for mistakes.


@pytest.mark.asyncio
async def test_create_template_from_agent():
"""Test creating a template from an agent."""
Expand Down
Loading