AI agents are becoming a new interface not only for finding people, but also for maintaining the profile that other agents search.
Instead of opening a profile page, editing a long self-introduction, guessing which keywords matter, and periodically refreshing it by hand, a user can now simply work with their agent as usual. As the user makes requests, rejects options, approves drafts, and reveals preferences, the agent can turn those signals into a structured, searchable profile.
In
The Impression Management process is the profile-maintenance layer.
While the Search and Contact process helps the user proactively find buyers or professionals, and the Lead Engagement process helps the user process inbound opportunities, both of those processes can uncover new attributes or preferences. Those new signals are then fed back into Impression Management, which refreshes the user’s public image.
In other words:
Search and lead handling reveal who the user is.
Impression Management turns that into something other agents can find.
This is important because a profile in the AI-agent era is not just a page written for humans. It is a semantic surface that agents use for search, explanation, and match reasoning.
A profile for agents, not just for humans
Traditional online profiles are mainly written for human readers.
They tend to optimize for readability, persuasion, and self-presentation. That still matters, but it is not enough when other AI agents are doing the retrieval.
An agent-searchable profile needs different properties:
- It must be structured enough for the agent to update continuously.
- It must be specific enough for semantic matching.
- It must be compact enough to avoid contradictions and bloat.
- It must be public enough to support discoverability.
- It must be fresh enough to reflect what the user actually wants now.
That is what impressions are doing in Opportunity Skill.
An impression is not just a sentence about the user. It is a profile unit designed for downstream semantic use.
For example, if a user repeatedly insists on strict type definitions, explicit interfaces, and long-term maintainability, the agent should not just remember that internally. It should be able to turn that pattern into a public impression that other agents can later match against.
That is why the skill instructions explicitly tell the agent to analyze tacit preferences, including negative preferences such as "remove X" or "do not use Y," and to summarize them as impressions with tags.
The agent-side entry points
The Impression Management process exposes four AI-facing functions. They are implemented in the skill’s scripts/callable_functions.py file and communicate with QuestMeet through GraphQL.
Reading the latest profile
def ai_read_user_info(access_token: str) -> Union[dict, bool, None]:
try:
response = httpx.post(
BASE_URL,
json={"query": "query AiReadUserInfo { aiReadUserInfo }"},
headers={"Authorization": f"Bearer {access_token}"},
trust_env=False,
timeout=20,
)
return response.json()["data"]["aiReadUserInfo"]
except Exception:
return False
Writing new impressions
impressions_with_tags_format = {
"type": "array",
"items": {
"type": "object",
"properties": {
"impression": {"type": "string"},
"tags": {"type": "array", "items": {"type": "string"}, "maxItems": 5}
},
"required": ["impression", "tags"],
"additionalProperties": False
}
}
def ai_create_impressions_buyer(access_token: str, impressions_with_tags: list) -> Union[str, bool, None]:
try:
jsonschema.validate(instance=impressions_with_tags, schema=impressions_with_tags_format)
response = httpx.post(
BASE_URL,
json={
"query": """
mutation AiCreateImpressionsBuyer($impressionsWithTags: JSON!) {
aiCreateImpressionsBuyer(impressionsWithTags: $impressionsWithTags)
}
""",
"variables": {"impressionsWithTags": impressions_with_tags},
},
headers={"Authorization": f"Bearer {access_token}"},
trust_env=False,
timeout=60,
)
return response.json()["data"]["aiCreateImpressionsBuyer"]
except Exception:
return False
ai_create_impressions_professional has the same shape, but writes impressions under the "Professional" perspective instead of "Buyer".
Deleting outdated impressions
def ai_delete_impressions(access_token: str, impressions: list[str]) -> Union[str, bool, None]:
try:
response = httpx.post(
BASE_URL,
json={
"query": """
mutation AiDeleteImpressions($impressions: [String!]!) {
aiDeleteImpressions(impressions: $impressions)
}
""",
"variables": {"impressions": impressions},
},
headers={"Authorization": f"Bearer {access_token}"},
trust_env=False,
timeout=20,
)
return response.json()["data"]["aiDeleteImpressions"]
except Exception:
return False
Return value semantics
These functions are small wrappers, but their return values matter because the agent owns the workflow.
| Function | Success | Expired auth | Other failure |
|---|---|---|---|
ai_read_user_info | dict | None | False |
ai_create_impressions_buyer | str | None | False |
ai_create_impressions_professional | str | None | False |
ai_delete_impressions | str | None | False |
This is the same pattern used in the other modules of Opportunity Skill.
If the token is expired, the server returns None, and the skill instructs the agent to re-authenticate and retry. If something else fails, the function returns False, and the agent should notify the user and stop instead of blindly retrying.
That distinction matters for agent workflows. A missing token is recoverable. A malformed request or server-side failure is not necessarily.
The two profile perspectives
One subtle but important design choice is that impressions are split by perspective.
The skill does not treat the user as a single flat identity. It distinguishes between:
- the user as a buyer
- the user as a professional
That distinction exists in both the skill instructions and the database model.
In the users table, each user has two external candidate IDs:
CREATE TABLE users (
user_id BIGINT GENERATED BY DEFAULT AS IDENTITY,
professional_id VARCHAR(50) UNIQUE DEFAULT gen_random_uuid()::text,
buyer_id VARCHAR(50) UNIQUE DEFAULT gen_random_uuid()::text,
email VARCHAR(255) UNIQUE,
...
name VARCHAR(50),
avatar VARCHAR(255),
description TEXT,
badges JSONB DEFAULT '[]'::jsonb,
...
);
And in the impressions table, every impression carries a perspective:
CREATE TABLE impressions (
impression_id BIGINT GENERATED BY DEFAULT AS IDENTITY,
user_id BIGINT NOT NULL,
perspective VARCHAR(50) NOT NULL,
content TEXT NOT NULL,
odd_embedding vector(1536),
even_embedding vector(1536),
is_public BOOLEAN NOT NULL,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (user_id, impression_id),
CONSTRAINT fk_impressions_user
FOREIGN KEY (user_id) REFERENCES users(user_id) ON DELETE CASCADE,
CONSTRAINT check_odd_or_even_embedding
CHECK ((odd_embedding IS NULL) <> (even_embedding IS NULL))
) PARTITION BY RANGE (user_id);
Why does that matter?
Because the same person may want to be found in two very different roles.
A founder may want to hire great engineers as a buyer, while also wanting to be discovered as a product strategist as a professional. Those are not the same search surface. Mixing them would blur the profile and produce weaker matches.
So Impression Management maintains two public semantic profiles for the same user, not one.
The data model
The Impression Management write path touches three core tables:
impressionstagsimpression_tags
Here is the simplified relationship:
SOCIAL SHARE CARD GENERATOR